Optimize tags and followedTags

followedTags:
- Moved the filtering to the reducer side, so that we don't do it every time. We can't rely on `createSelector` because the store will be invalidated on each `USER_STATE_POPULATE`, unfortunately.

tags:
- Memoize via re-reselect for the "ForUri" selector.
This commit is contained in:
infinite-persistence 2021-10-08 13:52:30 +08:00
parent 0c2c21b67e
commit 4b0318cd38
No known key found for this signature in database
GPG key ID: B9C3252EDC3D0AA0
4 changed files with 31 additions and 11 deletions

View file

@ -1,10 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { makeSelectTagsForUri } from 'redux/selectors/claims'; import { selectTagsForUri } from 'redux/selectors/claims';
import { selectFollowedTags } from 'redux/selectors/tags'; import { selectFollowedTags } from 'redux/selectors/tags';
import ClaimTags from './view'; import ClaimTags from './view';
const select = (state, props) => ({ const select = (state, props) => ({
tags: makeSelectTagsForUri(props.uri)(state), tags: selectTagsForUri(state, props.uri),
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
}); });

View file

@ -65,11 +65,22 @@ export default handleActions(
[ACTIONS.USER_STATE_POPULATE]: (state: TagState, action: { data: { tags: ?Array<string> } }) => { [ACTIONS.USER_STATE_POPULATE]: (state: TagState, action: { data: { tags: ?Array<string> } }) => {
const { tags } = action.data; const { tags } = action.data;
if (Array.isArray(tags)) { if (Array.isArray(tags)) {
const next = tags && tags.filter((tag) => typeof tag === 'string');
const prev = state.followedTags;
if (next && prev && prev.length === next.length && prev.every((value, index) => value === next[index])) {
// No changes
return state;
}
// New state
return { return {
...state, ...state,
followedTags: tags, followedTags: next || [],
}; };
} }
// Purge 'followedTags'
return { return {
...state, ...state,
}; };

View file

@ -270,6 +270,11 @@ export const makeSelectTotalPagesInChannelSearch = (uri: string) =>
return byChannel['pageCount']; return byChannel['pageCount'];
}); });
export const selectMetadataForUri = createCachedSelector(selectClaimForUri, (claim, uri) => {
const metadata = claim && claim.value;
return metadata || (claim === undefined ? undefined : null);
})((state, uri) => uri);
export const makeSelectMetadataForUri = (uri: string) => export const makeSelectMetadataForUri = (uri: string) =>
createSelector(makeSelectClaimForUri(uri), (claim) => { createSelector(makeSelectClaimForUri(uri), (claim) => {
const metadata = claim && claim.value; const metadata = claim && claim.value;
@ -537,6 +542,10 @@ export const makeSelectMyChannelPermUrlForName = (name: string) =>
return matchingClaim ? matchingClaim.permanent_url : null; return matchingClaim ? matchingClaim.permanent_url : null;
}); });
export const selectTagsForUri = createCachedSelector(selectMetadataForUri, (metadata: ?GenericMetadata) => {
return (metadata && metadata.tags) || [];
})((state, uri) => uri);
export const makeSelectTagsForUri = (uri: string) => export const makeSelectTagsForUri = (uri: string) =>
createSelector(makeSelectMetadataForUri(uri), (metadata: ?GenericMetadata) => { createSelector(makeSelectMetadataForUri(uri), (metadata: ?GenericMetadata) => {
return (metadata && metadata.tags) || []; return (metadata && metadata.tags) || [];

View file

@ -1,16 +1,16 @@
// @flow // @flow
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
const selectState = (state: { tags: TagState }) => state.tags || {}; type State = { tags: TagState };
const selectState = (state: State) => state.tags || {};
export const selectKnownTagsByName = createSelector(selectState, (state: TagState): KnownTags => state.knownTags); export const selectKnownTagsByName = createSelector(selectState, (state: TagState): KnownTags => state.knownTags);
export const selectFollowedTagsList = createSelector(selectState, (state: TagState): Array<string> => export const selectFollowedTagsList = (state: State) => selectState(state).followedTags;
state.followedTags.filter(tag => typeof tag === 'string')
);
export const selectFollowedTags = createSelector(selectFollowedTagsList, (followedTags: Array<string>): Array<Tag> => export const selectFollowedTags = createSelector(selectFollowedTagsList, (followedTags: Array<string>): Array<Tag> =>
followedTags.map(tag => ({ name: tag.toLowerCase() })).sort((a, b) => a.name.localeCompare(b.name)) followedTags.map((tag) => ({ name: tag.toLowerCase() })).sort((a, b) => a.name.localeCompare(b.name))
); );
export const selectUnfollowedTags = createSelector( export const selectUnfollowedTags = createSelector(
@ -19,7 +19,7 @@ export const selectUnfollowedTags = createSelector(
(tagsByName: KnownTags, followedTags: Array<string>): Array<Tag> => { (tagsByName: KnownTags, followedTags: Array<string>): Array<Tag> => {
const followedTagsSet = new Set(followedTags); const followedTagsSet = new Set(followedTags);
let tagsToReturn = []; let tagsToReturn = [];
Object.keys(tagsByName).forEach(key => { Object.keys(tagsByName).forEach((key) => {
if (!followedTagsSet.has(key)) { if (!followedTagsSet.has(key)) {
const { name } = tagsByName[key]; const { name } = tagsByName[key];
tagsToReturn.push({ name: name.toLowerCase() }); tagsToReturn.push({ name: name.toLowerCase() });
@ -31,6 +31,6 @@ export const selectUnfollowedTags = createSelector(
); );
export const makeSelectIsFollowingTag = (tag: string) => export const makeSelectIsFollowingTag = (tag: string) =>
createSelector(selectFollowedTags, followedTags => { createSelector(selectFollowedTags, (followedTags) => {
return followedTags.some(followedTag => followedTag.name === tag.toLowerCase()); return followedTags.some((followedTag) => followedTag.name === tag.toLowerCase());
}); });