Normy 数据缓存自动更新的边界:为什么顶层数组仍需手动更新,以及 2 种实用应对策略
【免费下载链接】normyAutomatic normalization and data updates for data fetching libraries (react-query, vue-query, trpc, swr, rtk-query and more)项目地址: https://gitcode.com/gh_mirrors/no/normy
如果你正在使用 react-query、SWR 或 RTK Query,那么 Normy 这个开源库值得了解:它负责对接口数据进行自动数据归一化,并在 mutation 成功后自动更新所有相关缓存,让你告别手动维护setQueryData的繁琐代码。但 Normy 的自动化有一个明确的边界——顶层数组的增删排序仍需手动处理。本文帮你看懂这个边界产生的原因,并给出 2 种简单实用的应对策略。
一、Normy 能自动做什么?先划清能力范围
Normy 的核心逻辑在 packages/normy/src/normalize.ts:所有带id的对象(无论嵌套多深)都会被按 id 存入一个共享的归一化存储,而各个查询的数据只保留对这些对象的"引用"。
于是当 mutation 返回{ id: '1', name: 'Name 1 Updated' }时,create-normalizer.ts 中的getQueriesToUpdate会:
- 找到所有"依赖"了
id: 1这个对象的查询; - 把归一化存储中的对象深合并(deep merge)为新值;
- 自动把新数据写回每一个相关查询的缓存。
一张表看懂 Normy 自动更新的边界
| 场景 | 是否自动 | 原因 |
|---|---|---|
| 修改对象的字段(如书名、作者名) | ✅ 自动 | 按 id 匹配并深合并,所有依赖查询同步更新 |
更新嵌套对象列表(如书籍的likedByUsers) | ✅ 自动 | 列表属于对象内部属性,整体合并 |
| 向顶层数组追加新对象 | ❌ 手动 | 新对象尚不在任何查询中,不存在依赖关系 |
| 从顶层数组删除对象 | ❌ 手动 | 同一对象可能出现在多个查询,库无法猜你的意图 |
| 对顶层数组重新排序 | ❌ 手动 | 顺序只存在于各查询自身,不在共享存储中 |
二、为什么顶层数组是个"例外"?
原因并不复杂,官方 README 在 Normalization of arrays 一节 里解释得很清楚:
- ADD_BOOK:Normy 不知道新对象应该被加进哪一个查询的数组,也不知道加在第几个位置;
- REMOVE_BOOK:一本书可能同时出现在"全部书籍列表"和"我的收藏"等多个查询里,Normy 不知道你想从哪些列表里移除它;
- SORT_BOOKS:数组的顺序信息只存在于各个查询自己的缓存中,共享的归一化存储里只有"对象内容",没有"数组成员关系"。
换个角度理解:Normy 的共享存储是一张"对象 id → 对象内容"的表,而"哪些对象出现在哪个列表、按什么顺序"是每个查询自己的事。所以**"内容"交给 Normy 自动同步,"成员关系"留给你手动决定**——这正是它的设计边界。
💡 注意:这个限制只针对顶层数组。只要对象列表是某个带 id 对象的属性(例如
book.likedByUsers),mutation 返回更新后的完整对象即可自动生效。
三、应对策略 1:手动补"数组",自动补"字段"
最简单直接的做法:顶层数组用数据获取库自带的 API 手动更新,对象内容交给 Normy 自动同步,两者分工协作。
以 react-query 为例,添加新书时只需在 mutation 的onSuccess里手动把新对象拼进['books']这个查询的缓存(示例见 examples/react-query/src/components/app.jsx):
onSuccess: mutationData => { queryClient.setQueryData(['books'], data => data.concat(mutationData)); }这之后,如果该书的作者信息又发生了变化,Normy 会自动把更新同步到书籍列表、书籍详情等所有相关查询——你完全不用手写map+ 判断 id 的代码。
🎯 适用场景:新增、替换单条数据等目标查询明确的操作,代码量最少。
四、应对策略 2:先用 getDependentQueries 定位,再批量更新
当一次操作影响多个查询的顶层数组时(比如从"全部列表""收藏""最近浏览"同时移除一本书),手动猜查询 key 很容易漏。这时可以利用 Normy 内置的依赖查询能力:
- getDependentQueries:传入一段包含对象的数据(如
{ id: '1' }),返回所有持有该对象的查询 key 列表; getDependentQueriesByIds:直接传 id 数组,效果相同。
拿到 key 列表后,你可以用 react-query 的invalidateQueries让相关查询重新拉取,或逐个setQueryData从数组中过滤掉目标对象。
🛠 同样的能力在 SWR 和 RTK Query 集成中也有对应实现,可参考 packages/normy-swr/src/SWRNormalizerProvider.tsx 与 packages/normy-rtk-query/src/index.ts。
🎯 适用场景:删除、排序等跨多个查询的操作,避免"漏更新某个列表"的 bug。
五、小结:把 Normy 用在刀刃上
- 对象字段变更→ 完全交给 Normy 自动更新,这是它最大的价值;
- 顶层数组增删排序→ 手动处理,查询明确就用策略 1,跨多查询就用策略 2 先定位再批量更新;
- 项目作者也表示,未来版本计划通过额外的"指针"机制把这些更新也自动化,值得持续关注 CHANGELOG。
理解这个边界后你会发现,Normy 的自动化覆盖了你日常 80% 以上的缓存同步场景,剩下的 20% 也有清晰的应对方案——这正是它"简单又实用"的地方。
【免费下载链接】normyAutomatic normalization and data updates for data fetching libraries (react-query, vue-query, trpc, swr, rtk-query and more)项目地址: https://gitcode.com/gh_mirrors/no/normy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考