尧图精选

Zustand 状态更新指南:浅合并、深层嵌套与 Immer/optics-ts/Ramda 不可变更新方案

🕒 发布时间:2026/9/18 1:26:31 📁 来源:尧图网络
Zustand 状态更新指南浅合并、深层嵌套与 Immer/optics-ts/Ramda 不可变更新方案【免费下载链接】zustand Bear necessities for state management in React项目地址: https://gitcode.com/gh_mirrors/zu/zustandZustand 通过set函数完成 store 内状态的更新默认采用浅合并shallow merge语义让扁平对象的状态更新变得极其简单而面对深层嵌套对象时则需要显式地以不可变方式逐层复制。本文以 docs/learn/guides/updating-state.md 为核心结合 src/vanilla.ts、src/middleware/immer.ts 与仓库测试用例系统讲解扁平更新、嵌套对象更新的四种主流方案手写展开、Immer、optics-ts、Ramda并剖析set的浅合并与replace标志的底层实现帮助你写出更简洁、类型安全、可维护的状态更新代码。扁平更新Flat updates直接调用set即可Zustand 更新状态非常简单调用create时传入的set函数传入新的状态或返回新状态的函数新状态会与 store 中已有状态进行浅合并shallow merge。这意味着你不需要手动展开旧状态只需要写出真正变化的字段。下面是官方文档给出的经典示例——一个管理firstName/lastName的 Person storeimport { create } from zustand type State { firstName: string lastName: string } type Action { updateFirstName: (firstName: State[firstName]) void updateLastName: (lastName: State[lastName]) void } // Create your store, which includes both state and (optionally) actions const usePersonStore createState Action()((set) ({ firstName: , lastName: , updateFirstName: (firstName) set(() ({ firstName: firstName })), updateLastName: (lastName) set(() ({ lastName: lastName })), }))在消费组件中用 selector 精准挑选需要的状态与 actionfunction App() { // select the needed state and actions, in this case, the firstName value // and the action updateFirstName const firstName usePersonStore((state) state.firstName) const updateFirstName usePersonStore((state) state.updateFirstName) return ( main label First name input // Update the firstName state onChange{(e) updateFirstName(e.currentTarget.value)} value{firstName} / /label p Hello, strong{firstName}!/strong /p /main ) }几点实操要点selector 驱动重渲染组件通过usePersonStore((state) state.firstName)订阅指定切片Zustand 的 React 绑定在 src/react.ts 中基于React.useSyncExternalStore实现只有被 selector 选中的值发生变化时组件才会重渲染因此不要省略 selector 以免订阅整个 store。两种传参形式set既接受对象字面量set({ firstName })也接受函数set((state) ({ ... }))。函数形式能拿到当前最新状态是基于旧值计算新值场景下的推荐写法。在 src/vanilla.ts 中setState会先判断partial是否为函数若是则以当前state为参数调用它得到最终nextState。Object.is变更检测setState内部用!Object.is(nextState, state)见 src/vanilla.ts判断状态是否真正变化未变化则跳过通知避免无效的订阅回调与重渲染。浅合并的底层原理浅合并的魔法来自 src/vanilla.ts 的setState实现。当未传入replace即replace为undefined且nextState是普通对象时state (replace ?? (typeof nextState ! object || nextState null)) ? (nextState as TState) : Object.assign({}, state, nextState)Object.assign({}, state, nextState)把nextState的字段覆盖到state的副本上只做一层合并这正是 docs/learn/guides/immutable-state-and-merging.md 中强调的因为set默认合并所以常见的计数更新可以直接写set((state) ({ count: state.count 1 }))而无需手写set((state) ({ ...state, count: state.count 1 }))若nextState是null、undefined等非对象则直接整体替换为nextState。深层嵌套对象Deeply nested object必须不可变地逐层更新当状态是如下这类深层嵌套结构时情况就没那么简单了type State { deep: { nested: { obj: { count: number } } } }由于set只做一层浅合并deep字段下层的对象会整段被替换因此更新嵌套状态必须确保过程是**不可变immutable**的——保留未变部分的对象引用只替换变化路径上的对象。常规方案手动展开spread逐层复制与 React / Redux 的常规做法一致用展开运算符...逐层复制状态对象并手动把新值合并进去normalInc: () set((state) ({ deep: { ...state.deep, nested: { ...state.deep.nested, obj: { ...state.deep.nested.obj, count: state.deep.nested.obj.count 1 } } } })),这段代码功能正确但非常冗长——每加深一层就要多写一层...展开。层级越深样板代码越多也越容易在展开时漏掉某个层级导致意外的引用共享或状态丢失。下面介绍几种让生活更轻松的替代方案。使用 Immer用可变写法实现不可变更新很多人使用 Immer 来更新嵌套值。Immer 可以在任何需要更新嵌套状态的场景下使用例如 React、Redux当然也包括 Zustand。利用 Immer 可以大幅缩短深层嵌套对象的更新代码。来看示例immerInc: () set(produce((state: State) { state.deep.nested.obj.count })),更新量减少非常明显Immer 通过 Proxy 提供可变的draft你在回调里直接修改嵌套属性Immer 会记录这些修改并产出一个新的不可变状态。配合 immer middleware 使用如果不想在每个 action 里手动包一层produceZustand 官方提供了 immer 中间件 src/middleware/immer.ts。它的核心实现是重写setStatestore.setState (updater, replace, ...args) { const nextState ( typeof updater function ? produce(updater as any) : updater ) as ((s: T) T) | T | PartialT return set(nextState, replace as any, ...args) }即当 updater 是函数时自动套上produce于是你可以直接写import { create } from zustand import { immer } from zustand/middleware/immer export const useCountStore createState Actions()( immer((set) ({ count: 0, increment: (qty: number) set((state) { state.count qty }), decrement: (qty: number) set((state) { state.count - qty }), })), )注意使用中间件时create后面需要保留多余的括号createT()(...)具体原因见 advanced-typescript.md。需要先安装 Immer 作为直接依赖npm install immer。仓库的 tests/immer.test.tsx 验证了函数 updater 会经produce处理同时 tests/middlewareTypes.test.tsx 验证了其类型推导——在回调中直接state.count get().count 1也能获得完整类型检查。使用 Immer 的注意事项Gotchas请务必阅读官方文档中列出的 immer-middleware.md 中记录的注意事项订阅没有被调用使用 Immer 时务必遵守 Immer 的规则。例如类对象需要添加[immerable] true才能被 Proxy 代理。如果没这么做Immer 仍会直接修改对象而非通过代理从而同时改动当前状态由于 Zustand 会用Object.is检查状态是否真的变化见 src/vanilla.ts前后状态相等时就会跳过订阅回调。replace标志依然生效中间件把replace原样透传给底层settests/immer.test.tsx 验证了setState({ a: 9 }, true)会整体替换状态。使用 optics-ts无 Proxy 的函数式光学另一个选项是使用 optics-tsopticsInc: () set(O.modify(O.opticState().path(deep.nested.obj.count))((c) c 1)),optics-ts 的核心思路是光学optic——用O.opticState().path(deep.nested.obj.count)描述状态中的目标路径再用O.modify(optic)(fn)在该路径上应用变换函数。与 Immer 不同的是optics-ts 不使用 Proxy也不依赖可变语法整个更新过程是纯函数式的你传入的是一个显式的路径描述与纯函数。使用 Ramda函数式工具库的路径修改还可以使用 RamdaramdaInc: () set(R.modifyPath([deep, nested, obj, count], (c) c 1)),R.modifyPath接收一个路径数组或路径描述和一个变换函数同样以纯函数方式返回更新后的新对象不会原地修改。Ramda 和 optics-ts 都带有完善的类型定义与 TypeScript 配合良好Both ramda and optics-ts also work with types。四种方案对比与选型建议方案代码量实现机制类型支持适用场景手动展开spread冗长逐层...复制原生良好层级浅、更新频率低Immer / immer 中间件极少Proxy 可变 draft自动不可变中间件提供完整推导深层嵌套、复杂不可变数据optics-ts少光学optic路径纯函数完善偏好函数式、需显式路径Ramda少modifyPath纯函数完善已在用 Ramda 的工具函数风格选型提示若项目已经有 Immer 依赖或状态结构很深且更新频繁推荐 immer 中间件——它把produce封装进set无需在每个 action 手动包裹若偏好函数式风格、不希望引入 Proxy 与可变语法optics-ts 与 Ramda 都是干净的选择且两者都支持类型任何情况下都不要忘记set只做一层浅合并深层嵌套必须借助上述任一方案保证不可变性。结合源码理解set合并、替换与更新通知把 docs/learn/guides/updating-state.md 与 immutable-state-and-merging.md 两篇文档对照 src/vanilla.ts 的实现可以得到完整的set行为画像接受对象或函数partial为函数时先以当前state调用得到nextState变更检测Object.is(nextState, state)相等则直接返回不触发任何通知合并 or 替换未传replace默认undefined且nextState为对象 →Object.assign({}, state, nextState)浅合并传了replace true或nextState不是对象 → 直接整体替换通知订阅者listeners.forEach((listener) listener(state, previousState))向所有订阅者广播新状态与旧状态。replace标志的用法来自 immutable-state-and-merging.mdset((state) newState, true)当你需要禁用合并行为、用全新对象整体覆盖状态例如重置 store 或替换整个数据快照时使用。这个标志在 src/vanilla.ts 的类型定义中也有体现replace?: false时允许PartialT而replace: true时要求完整的新状态。延伸阅读immutable-state-and-merging.md浅合并、嵌套对象更新与replace标志的官方说明immer-middleware.mdimmer 中间件的安装、使用与 Gotchas 完整文档src/vanilla.tscreateStore/setState核心实现src/middleware/immer.tsimmer 中间件源码tests/immer.test.tsximmer 中间件行为测试官方在线演示https://stackblitz.com/edit/vitejs-vite-j6bjdyguupdating-state 配套 Demo【免费下载链接】zustand Bear necessities for state management in React项目地址: https://gitcode.com/gh_mirrors/zu/zustand创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →