You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NGRX选择器重复执行问题:如何复用查询并利用Memoization?

NGRX选择器复用与缓存问题

我在NGRX中维护了分组(groups)和单词(words)的归一化数据:每个分组存储所属单词ID的数组。目前有两个逻辑完全一致的选择器,但无法复用缓存,导致重复编写冗余代码:

  1. 传入groupId获取对应单词:
export const selectWordsByGroupId = (groupId: string) => createSelector(
    selectWordEntities,
    selectGroupEntities,
    (wordEntities, groupEntities) => {
        const wordIds = groupEntities[groupId].wordIds;
        return wordIds.map(wordId => wordEntities[wordId])
    }
);
  1. 通过路由参数的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)
    • 复合选择器:组合原子选择器计算衍生数据,提高复用性
  • 纯函数约束:选择器必须是纯函数,不能包含副作用、异步操作,确保缓存有效性

内容的提问来源于stack exchange,提问作者Max Tuzenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 08:42:14