自定义合并策略:深入ngrx-store-localstorage的mergeReducer与deepmerge数组覆盖机制
【免费下载链接】ngrx-store-localstorageSimple syncing between @ngrx store and local storage项目地址: https://gitcode.com/gh_mirrors/ng/ngrx-store-localstorage
ngrx-store-localstorage 是面向 @ngrx/store 的状态持久化插件,负责在 NgRx 状态仓库与浏览器 localStorage 之间双向同步,实现页面刷新后状态自动恢复。本文聚焦两个核心机制:mergeReducer自定义合并策略,以及默认合并中 deepmerge 的数组覆盖行为,帮新手快速搞懂状态"复活"的完整链路。
🧩 先搞懂合并发生在哪:localStorageSync 的三步包装
加载localStorageSync配置后,每个 action 都会先经过库的包装层,再进入你的业务 reducer,调用顺序在projects/lib/src/lib/index.ts#L336-L351:
- 用你的 reducer 算出默认状态,得到
nextState; - 执行
mergeReducer(nextState, rehydratedState, action),把从 localStorage 读出的水合状态合并进去; - 合并结果再交给你的真实 reducer 产出最终状态。
也就是说:无论默认还是自定义合并策略,都发生在业务 reducer 之前,它决定的是 reducer 看到的"初始状态"。
⚙️ 默认合并策略:deep merge 深合并 + 数组覆盖
不传mergeReducer时,库自动使用内置的defaultMergeReducer,源码见projects/lib/src/lib/index.ts#L277-L299,三个关键点:
- 时机:仅当 action 为
@ngrx/store/init或@ngrx/store/update-reducers时才合并,其余动作原样放行; - 方式:调用
deepmerge做深合并——对象逐键合并,localStorage 里存的属性覆盖默认值,没存的属性保留默认值; - 数组覆盖:给 deepmerge 传入了自定义
arrayMerge回调overwriteMerge,其实现只有一行——直接返回源数组(即存储中的数组)。
📋 为什么数组要"覆盖":deepmerge 默认拼接的坑
deepmerge 对数组的默认行为是把两边数组拼接(先 destination 后 source)。假设:
- 代码里的默认状态:
tabs: [1, 2] - localStorage 里已存的快照:
tabs: [1, 2, 3]
若按默认行为合并,刷新后会得到[1, 2, 1, 2, 3]——列表凭空多出一堆重复项。而overwriteMerge让数组遵循"最后一次写入获胜"的语义:存储是最新快照,数组整体替换代码默认值,得到干净的[1, 2, 3]。对象仍走深合并,"存的覆盖、没存的保留"依然成立。这正是刷新后列表重复问题的根源,也是默认策略要显式处理数组的原因。
🛠️ 如何写自定义 mergeReducer:签名与常见场景
配置接口中mergeReducer的类型定义在projects/lib/src/lib/index.ts#L375-L385:
mergeReducer?: (state: any, rehydratedState: any, action: any) => any;state:当前内存中的初始状态(reducer 的默认状态)rehydratedState:按keys配置从 localStorage 读出的水合状态action:当前动作,可按动作类型分支处理
返回值就是即将交给业务 reducer 的初始状态。常见使用场景:
- 部分覆盖:只接受存储中的某些字段,忽略其余;
- 混合策略:某些 feature 全量深合并,某些只取指定字段;
- 数据迁移:合并阶段把旧版存储结构转换成新版结构。
🔧 实战示例:按 feature 混合合并
官方测试spec/index.spec.ts#L751-L800给出了典型示例:feature1、feature2分片存储,自定义合并函数对两者采用不同策略:
const mergeReducer = (state, rehydratedState, action) => ({ ...state, feature1: { slice11: rehydratedState.feature1.slice11 }, // 只取一个字段 feature2: deepmerge(state.feature2, rehydratedState.feature2), // 完整深合并 });配合localStorageSync({ keys: [...], rehydrate: true, mergeReducer })后,最终状态里feature1只保留了存储值中的slice11,feature2则完成了完整深合并。可见合并策略完全交给开发者,"字段挑选"与"deepmerge"可以在同一应用里自由搭配。
⚡ 总结:合并策略速查清单
- 不传
mergeReducer?默认策略 = 深合并 + 数组整体覆盖,多数场景直接可用; - 想精细控制"恢复哪些字段"?传入自定义函数,返回合并后的初始状态即可;
- 遇到恢复后数组重复?检查是否误用了 deepmerge 的默认
arrayMerge(拼接); - 合并只发生在 init / update-reducers 动作、且在业务 reducer 之前,不会干扰后续任何 action。
更多配置项说明可参考 README.md 中mergeReducer的官方描述(第 98 行)。
【免费下载链接】ngrx-store-localstorageSimple syncing between @ngrx store and local storage项目地址: https://gitcode.com/gh_mirrors/ng/ngrx-store-localstorage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考