NGRX选择器重复执行问题:如何复用查询并利用Memoization?
NGRX选择器复用与缓存问题
我在NGRX中维护了分组(groups)和单词(words)的归一化数据:每个分组存储所属单词ID的数组。目前有两个逻辑完全一致的选择器,但无法复用缓存,导致重复编写冗余代码:
- 传入groupId获取对应单词:
export const selectWordsByGroupId = (groupId: string) => createSelector( selectWordEntities, selectGroupEntities, (wordEntities, groupEntities) => { const wordIds = groupEntities[groupId].wordIds; return wordIds.map(wordId => wordEntities[wordId]) } );
- 通过路由参数的groupId获取单词:
getCurrentGroupExerciseWords: createSelector( getParams, selectGroupEntities, selectWordEntities, (params, groupEntities, wordEntities) => { const wordIds = groupEntities[params.groupId].wordIds; return wordIds.map(wordId => wordEntities[wordId]) } ),
尝试在投影器中调用已有的selectWordsByGroupId,但返回的是选择器实例而非数据,且工厂选择器的缓存复用问题未解决。
解决方案
1. 提取公共投影函数
将核心逻辑抽成纯函数,让两个选择器共享:
// 公共逻辑:根据groupId、单词实体、分组实体获取单词列表 const getWordsForGroup = ( groupId: string, wordEntities: Record<string, Word>, groupEntities: Record<string, Group> ) => { const wordIds = groupEntities[groupId]?.wordIds || []; return wordIds.map(wordId => wordEntities[wordId]).filter(Boolean); };
修改后的选择器:
// 传入groupId的版本 export const selectWordsByGroupId = (groupId: string) => createSelector( selectWordEntities, selectGroupEntities, (wordEntities, groupEntities) => getWordsForGroup(groupId, wordEntities, groupEntities) ); // 路由参数版本 getCurrentGroupExerciseWords: createSelector( getParams, selectWordEntities, selectGroupEntities, (params, wordEntities, groupEntities) => getWordsForGroup(params.groupId, wordEntities, groupEntities) ),
2. 组合选择器实现缓存复用
如果想让路由参数版本复用selectWordsByGroupId的缓存,可以先提取当前groupId的选择器,再组合:
// 先获取路由参数中的groupId const selectCurrentGroupId = createSelector( getParams, (params) => params.groupId ); // 组合出最终的单词列表选择器 export const getCurrentGroupExerciseWords = createSelector( selectCurrentGroupId, selectWordEntities, selectGroupEntities, (groupId, wordEntities, groupEntities) => { if (!groupId) return []; return getWordsForGroup(groupId, wordEntities, groupEntities); } );
NGRX选择器会自动对输入参数做缓存:只要groupId、wordEntities、groupEntities不变,就直接返回缓存结果,无需重复计算。
3. 工厂选择器的缓存说明
NGRX的工厂选择器(带参数的选择器)本身自带缓存:相同的groupId参数会复用之前的计算结果。如果需要更细粒度的缓存控制,可以用Lodash的_.memoize包装公共函数,但一般NGRX内置的缓存机制足够应对大部分场景。
NGRX数据结构设计要点
- 归一化存储:将实体(如words、groups)存在字典(
Record<string, Entity>)中,用ID关联,避免嵌套重复数据,这是官方推荐的标准模式 - 状态拆分:按业务模块拆分feature state,保持状态树扁平、清晰,避免单一大状态
- 选择器分层:
- 原子选择器:直接从state中提取原始字段(如
selectWordEntities) - 复合选择器:组合原子选择器计算衍生数据,提高复用性
- 原子选择器:直接从state中提取原始字段(如
- 纯函数约束:选择器必须是纯函数,不能包含副作用、异步操作,确保缓存有效性
内容的提问来源于stack exchange,提问作者Max Tuzenko
相关产品推荐
相关产品推荐

