ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

NgRx Data Entity Reducer:实体缓存主 Reducer、集合 Reducer 与 MetaReducer 定制机制详解

NgRx Data Entity Reducer:实体缓存主 Reducer、集合 Reducer 与 MetaReducer 定制机制详解 前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载Entity Reducer 是 NgRx Data 实体缓存EntityCache的主 reducer负责把每一条EntityAction路由到对应实体集合的 reducer并在需要时初始化集合状态。本篇基于官方指南 entity-reducer.md结合ngrx/data包的源码modules/data/src/reducers/目录逐层解析主 reducer 的分发逻辑、EntityCollectionReducer注册表、默认集合 reducer 的EntityOp分派机制、集合状态初始化以及 EntityCache 级动作与两级 MetaReducer 定制点帮助你从原理到扩展点完整掌握 NgRx Data 的缓存更新链路。需要说明ngrx/data包目前处于维护模式maintenance mode变更仅限于关键缺陷修复官方不再为其增加新特性。一、Entity Reducer 总览EntityCacheReducerFactory 是核心Entity Reducer 是存储实体缓存中所有实体集合的master reducer。ngrx/data并没有一个具名的 entity reducer 类型而是依赖EntityCacheReducerFactory.create()方法生产该 reducer。它的签名本质上是一个 NgRx 的ActionReducerEntityCache, EntityAction输入EntityCache状态按实体类型名索引的EntityCollection哈希和一个EntityAction动作输出新的EntityCache状态。EntityCache的类型定义非常简洁就是一个以集合名为键的映射见 entity-cache.tsexport interface EntityCache { // Must be any since we dont know what type of collections we will have [name: string]: EntityCollectionany; }这个 reducer 只响应两类动作EntityCache 级动作较少见针对某个实体集合的EntityAction通常情况。其他所有Action都会被忽略reducer 直接原样返回传入的state。从源码看EntityCacheReducerFactory.create() 返回的 reducer 先对EntityCacheAction常量做 switch 匹配处理缓存级动作然后判断// Apply entity collection reducer if this is a valid EntityAction for a collection const payload action.payload; if (payload payload.entityName payload.entityOp !payload.error) { return this.applyCollectionReducer(entityCache, action as EntityAction); } // Not a valid EntityAction return entityCache;也就是说当前源码以payload.entityName属性作为这是一个 EntityAction的判据指南中写作entityType与早期版本的命名一致并且会跳过携带error的 payload。主 reducer 的主要工作可归纳为五步从state中提取动作所指向实体类型的EntityCollection必要时创建一个新的、已初始化的实体集合获取或创建该实体类型的EntityCollectionReducer用该集合和动作调用entity collection reducer用entity collection reducer返回的新集合替换EntityCache中原集合。上述流程对应源码中的 applyCollectionReducerprivate applyCollectionReducer(cache: EntityCache {}, action: EntityAction) { const entityName action.payload.entityName; const collection cache[entityName]; const reducer this.entityCollectionReducerRegistry.getOrCreateReducer(entityName); let newCollection: EntityCollection; try { newCollection collection ? reducer(collection, action) : reducer(this.entityCollectionCreator.create(entityName), action); } catch (error: any) { this.logger.error(error); action.payload.error error; } return action.payload.error || collection newCollection! ? cache : { ...cache, [entityName]: newCollection! }; }几个值得注意的实现细节集合不存在时通过注入的EntityCollectionCreator现场创建一个初始集合集合 reducer 抛错时不会让 store 崩溃而是把错误挂到action.payload.error并记录日志只有当集合引用真正变化时才返回新的缓存对象{ ...cache, [entityName]: newCollection }否则复用原cache引用——这是 NgRx 中减少无谓重渲染的常见做法。二、EntityCollectionReducers 与注册表EntityCollectionReducer把actions应用到 NgRx store 中EntityCache里的EntityCollection上。其类型定义见 entity-collection-reducer.tsexport type EntityCollectionReducerT any ( collection: EntityCollectionT, action: EntityAction ) EntityCollectionT;每种实体类型永远有一个 reducerEntityCollectionReducerRegistry维护一个注册表entity-collection-reducer-registry.ts。当它找不到某实体类型的 reducer 时会借助注入的EntityCollectionReducerFactory创建一个并把它注册起来供下次复用getOrCreateReducerT(entityName: string): EntityCollectionReducerT { let reducer: EntityCollectionReducerT this.entityCollectionReducers[entityName]; if (!reducer) { reducer this.entityCollectionReducerFactory.createT(entityName); reducer this.registerReducerT(entityName, reducer); this.entityCollectionReducers[entityName] reducer; } return reducer; }注册自定义集合 reducer你可以为某个实体类型创建自定义 reducer并通过EntityCollectionReducerRegistry.registerReducer()直接注册// 注册单个 registry.registerReducer(Hero, myHeroReducer); registry.registerReducer(Villain, myVillainReducer); // 批量注册reducerMap 是以实体类型名为键的 reducer 哈希 registry.registerReducers({ Hero: myHeroReducer, Villain: myVillainReducer, });对应源码中registerReducers只是遍历键名逐个调用registerReducer并且每次注册都会先把 reducer 包进 meta-MetaReducer见 MetaReducer 章节再写入注册表registerReducerT(entityName: string, reducer: EntityCollectionReducerT): EntityCollectionReducerT { reducer this.entityCollectionMetaReducer(reducer as any); return (this.entityCollectionReducers[entityName.trim()] reducer); }相关行为在 entity-collection-reducer-registry.spec.ts 中有测试覆盖。三、默认 EntityCollectionReducer按 EntityOp 分派EntityCollectionReducerFactory创建的默认 reducer 充分利用了 NgRxEntityAdapter的能力并由应用的entity metadata引导。默认 reducer 的核心逻辑极其精简return function entityCollectionReducer( collection: EntityCollectionT, action: EntityAction ): EntityCollectionT { const reducerMethod methods[action.payload.entityOp]; return reducerMethod ? reducerMethod(collection, action) : collection; };它以EntityAction.payload.entityOp为索引从一张EntityOp→ 处理方法的映射表中找到对应方法并调用找不到方法就直接返回原集合——所以大量EntityOp值是被忽略的集合保持不变。EntityCollectionReducerMethods 中这张映射表覆盖了三大类操作类别典型 EntityOp行为特征查询类QUERY_ALL、QUERY_BY_KEY、QUERY_MANY及各自的_ERROR/_SUCCESS更新集合数据并翻转loaded/loading标志持久化类SAVE_ADD_ONE、SAVE_UPDATE_MANY、SAVE_DELETE_MANY、SAVE_UPSERT_*等面向 NgRx Data 的persist$效果通常只更新集合数据或仅翻转loading标志真正落库由 effect 完成纯缓存类ADD_ONE、ADD_MANY、ADD_ALL、REMOVE_ONE、UPDATE_MANY、UPSERT_ONE等直接增删改集合中的实体不可变性是核心原则这些 reducer 并不会真正修改原集合或其中的对象而是复制集合、只更新副本中的对象——这与 redux/NgRx 模式的不可变状态要求一致。默认集合 reducer 实际执行了哪些缓存变更操作可以参考 NgRx Entity 的 EntityAdapter 集合方法。而默认 reducer 究竟如何工作的最终权威依据是EntityCollectionReducerFactory类及其测试entity-collection-reducer.spec.ts。四、初始化集合状态NgRxDataModule会向 NgRx Data store 添加一个空的EntityCache其中尚无任何集合。当主EntityReducer在state中找不到动作所指向实体类型的集合时它会借助注入到EntityCacheReducerFactory中的EntityCollectionCreator创建一个新初始化集合。EntityCollectionCreator.create() 的逻辑是createT any, S extends EntityCollectionT EntityCollectionT(entityName: string): S { const def this.entityDefinitionService this.entityDefinitionService.getDefinitionT(entityName, false); const initialState def def.initialState; return S(initialState || createEmptyEntityCollectionT(entityName)); }creator优先返回该实体EntityDefinition中initialState所定义的初始化集合若该实体类型没有definition或 definition 中没有initialState属性则返回一个空集合骨架createEmptyEntityCollectionids: []、entities: {}、loaded/loading均为false、空的changeState随后entity reducer把这个新集合作为entity collection reducer的state参数传入。这也意味着为实体提供EntityDefinition含initialState是定制初始集合状态的官方入口实体元数据定义详见 entity-metadata 指南。五、定制 Entity Reducer 行为的三个层级你可以按粒度由小到大逐层替换替换某个集合 reducer通过注册自定义替代EntityCollectionReducerRegistry.registerReducer/registerReducers替换默认集合 reducer 工厂向 EntityCollectionReducerFactory 提供自定义替代如通过 DI 覆盖 useClass替换主 Entity Reducer向EntityCacheReducerFactory提供自定义替代。不过更常见的诉求不是整体替换而是在某个集合 reducer 之前或之后追加一些额外的 reducer 逻辑——这时应考虑 MetaReducers。六、EntityCache 级动作少数动作直接作用于整个实体缓存。SET_ENTITY_CACHE会用动作 payload 中的对象替换整个缓存相当于把实体缓存重新初始化到一个已知状态MERGE_ENTITY_CACHE会用动作 payload 中存在的集合替换当前缓存中的对应集合其余集合保持不变。重要提示原文档强调MERGE_ENTITY_CACHE是替换replace当前缓存的集合而不是把 payload 中的实体合并merge进现有集合——不要望文生义。对应地当前仓库源码中缓存级动作定义在 entity-cache-action.ts包括EntityCacheAction.SET_ENTITY_CACHE动作类SetEntityCache注释明确写着危险但有用典型场景是在应用启动时从浏览器本地存储重新水合re-hydrateEntityCacheEntityCacheAction.MERGE_QUERY_SET动作类MergeQuerySet把一次多集合查询结果按MergeStrategy默认MergeStrategy.PreserveChanges合入各集合以及CLEAR_COLLECTIONS、LOAD_COLLECTIONS和SAVE_ENTITIES系列动作。需要注意这些动作不是EntityAction没有entityName/entityOppayload因此不会被主 reducer 的集合分派逻辑捕获而是在入口 switch 中被直接处理例如case EntityCacheAction.SET_ENTITY_CACHE: { // Completely replace the EntityCache. Be careful! return action.payload.cache; }entity-cache-dispatcher.ts 为这些动作提供了便捷的派发方法setEntityCache(cache, tag)、mergeQuerySet(querySet, mergeStrategy, tag)、clearCollections(collections, tag)、loadCollections(collections, tag)等并支持可选的tag参数用于从应用角度标识操作来源。离线与批量回滚场景这类缓存级动作非常适合支撑离线场景或同时对多个集合进行回滚订阅EntityServices.entityCache$选择器当缓存变化时将其序列化写入浏览器 local storage建议加几秒debounce以减少频繁写入应用重新启动时即使处于断网状态也可以派发SET_ENTITY_CACHE动作用本地存储恢复实体缓存的初始状态或在错误恢复、what-if 场景中派发MERGE_ENTITY_CACHE把选定的集合回滚到某个已知状态。SET_ENTITY_CACHE与缓存恢复动作的用法示例可参考 entity-cache-reducer.spec.ts 中 EntityCache-level actions 一节的测试注释中明确提到 re-hydrating from browser storage / offline-capable app。如果你希望创建并 reduce额外的、作用于整个缓存的动作应考虑下文介绍的 EntityCache MetaReducer。七、MetaReducersNgRx Data 的两级定制点ngrx/store支持MetaReducers它们可以检查流经 store 的动作并有可能改变 store 中的状态MetaReducer 是接受一个 reducer、返回一个 reducer的函数NgRx 把它们与其他 reducer 组合成责任链chain of responsibilityNgRx 调用 MetaReducer 返回的 reducer 时与调用任何 reducer 一样传入state和action它可以对 state 和 action 为所欲为记录日志、自行处理动作、委托给传入的 reducer、后处理更新后的 state或全部组合。提醒动作本身是不可变的不要修改 action需要改请克隆它。和所有 reducer 一样传入 MetaReducer 之 reducer 的 state 只是该 reducer 作用域内的那部分 store。NgRx Data 支持两级 MetaReducerEntityCache MetaReducer作用域为整个实体缓存EntityCollection MetaReducer作用域为某个特定集合。EntityCache MetaReducersEntityCache MetaReducer帮助你检查和作用于整个实体缓存的动作。例如你可以定义自定义动作并配合 EntityCache MetaReducer 同时更新多个集合。其 reducer 必须满足三条要求始终返回完整的实体缓存同步返回不能等待服务器响应绝不修改原 action需要改动请克隆。用法示例可参考 entity-data.module.spec.ts。注入侧NgRxDataModule/provideEntityData通过ENTITY_CACHE_META_REDUCERS多值注入令牌收集这些 MetaReducer见 constants.ts 与 provide-entity-data.ts 中对ENTITY_CACHE_META_REDUCERS/ENTITY_COLLECTION_META_REDUCERS的 provide 配置。Entity Collection MetaReducersEntity collection MetaReducer以entity collection reducer作为 reducer 参数返回一个新的entity collection reducer。新 reducer 收到的正是原本会传给原 reducer 的EntityCollection与EntityAction参数它可以记录动作日志把动作转换为同一集合的另一个动作调用原 reducer对原 reducer 的结果做后处理。新的 entity collection reducer 必须满足三条要求始终返回同一实体的EntityCollection同步返回不能等待服务器响应绝不修改原 action需要改动请克隆。与 Store MetaReducers 的区别entity collection MetaReducer虽然仿照 NgRx Store 的MetaReducerStore MetaReducer建模但有几个关键差异Store MetaReducer面向的是store reducer它包装 store reducer、看到所有动作、可以更新其作用域内的任意状态但 Store MetaReducer既看不到也包装不了entity collection reducer——这些集合 reducer 位于注册在 NgRx Data feature 中的EntityCache Reducer内部entity collection MetaReducer则专注于对单个目标实体集合的操作它包装所有 entity collection reducer注意它无法访问其他集合、entity cache或 store 中的任何其他状态。如果你需要跨集合的 MetaReducer请改用 EntityCache MetaReducer。向工厂提供 Entity MetaReducers创建一个或多个entity collection MetaReducers放入一个数组然后在导入NgRxDataModule或调用provideEntityData的位置用ENTITY_COLLECTION_META_REDUCERS注入令牌提供这个数组。EntityCollectionReducerRegistry会注入该数组并用 NgRx 的compose把这一组 MetaReducer 组合成单一的meta-MetaReducer数组中靠前的MetaReducer 包装靠后的// EntityCollectionReducerRegistry 构造函数节选 this.entityCollectionMetaReducer compose.apply( null, entityCollectionMetaReducers || [] ) as any;当工厂注册一个EntityCollectionReducer包括它自行创建的那些时会先把它包进meta-MetaReducer再写入注册表。此后所有派发到 store 的EntityActions在进出某个实体专属 reducer 的过程中都会经过这层包装。如何创建并提供entity collection MetaReducers的完整示例可参考 entity-cache-reducer.spec.ts。八、小结NgRx Data 的 Entity Reducer 体系是一台两级路由机器主 reducerEntityCacheReducerFactory.create()先处理缓存级动作SET_ENTITY_CACHE等再把EntityAction按entityName路由集合 reducer由EntityCollectionReducerFactory创建、EntityCollectionReducerRegistry注册与缓存按EntityOp分派到基于EntityAdapter的具体方法集合初始化由EntityCollectionCreator结合EntityDefinition.initialState完成定制有三个替换层级集合 reducer / 集合工厂 / 主 reducer 工厂加两个 MetaReducer 作用域EntityCache 级、EntityCollection 级全部通过 DI 令牌ENTITY_CACHE_META_REDUCERS、ENTITY_COLLECTION_META_REDUCERS或注册表 API 注入。关键源码入口entity-cache-reducer.ts、entity-collection-reducer.ts、entity-collection-reducer-registry.ts、entity-collection-creator.ts、entity-cache-action.ts对应测试位于modules/data/spec/reducers/与 entity-data.module.spec.ts。赞分享前端状态管理【免费下载链接】platformReactive State for Angular项目地址https://gitcode.com/gh_mirrors/pl/platform点击查看免费下载相关推荐Redux Reducer 重构实战用函数式分解与 Reducer 组合拆解单体逻辑Redux Reducer 重构实战用函数式分解与 Reducer 组合拆解单体逻辑 本文以 Redux 官方文档 Refactoring Reducers前端Rematch config.redux 配置详解定制 Redux 的 initialState、reducer 合并、中间件与 DevtoolsRematch config.redux 配置详解定制 Redux 的 initialState、reducer 合并、中间件与 Devtools Remat前端Redux Reducer 结构组织完全指南从单一 reducer 到 reducer 组合与规范化状态Redux Reducer 结构组织完全指南从单一 reducer 到 reducer 组合与规范化状态 Redux 的核心是一个简单的设计模式所有写逻前端上一篇英雄联盟录像编辑终极指南免费开源工具League Director完全教程下一篇终极指南如何用RimSort彻底解决RimWorld模组冲突问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表