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

发布时间:2026/11/5 12:04:12

Zustand 状态更新指南:浅合并、深层嵌套与 Immer/optics-ts/Ramda 不可变更新方案 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),仅供参考
延伸阅读

更多相关文章

2026/11/2 22:38:01

VS Code配置STM32开发环境:ARM GCC+Cortex-Debug全链路指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 22:38:00

ADB安卓调试工具详解:安装配置、常用命令与避坑指南

1. ADB到底是个啥,以及装好它的正确姿势1.1 先搞明白:ADB不是“一键刷机”那种黑科技很多刚接触安卓开发或者喜欢折腾手机的朋友,第一次听到“ADB”都会觉得这玩意儿挺神秘。其实说白了,ADB(Android Debug Bridge&…

2026/11/3 15:20:34

Python从Word批量提取校徽并构建FTS5检索与感知哈希去重流水线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 11:39:15

PostgreSQL数据类型选择指南与性能优化实践

1. PostgreSQL数据类型概述PostgreSQL作为一款功能强大的开源关系型数据库,其数据类型系统之丰富远超大多数同类产品。我在实际项目中最常遇到的问题是:面对十几种数字类型、多种字符串类型以及各种特殊类型时,开发团队往往凭直觉选择&#x…

2026/11/5 11:34:12

Simulink S-function执行时序与状态管理原理详解

简介:本资源是面向MATLAB/Simulink初学者与控制系统建模学习者的S-Function入门实践包,聚焦自定义系统函数开发这一核心能力,解决标准库无法覆盖的特定数学运算与动态逻辑建模需求。压缩包共4个文件(2个M文件实现核心算法逻辑&…

2026/11/5 11:34:12

低空经济三维智能感知与实时调度技术解析

1. 低空经济三维空间智能感知与管控体系概述低空经济作为新兴经济形态,正在全球范围内快速发展。根据国际航空运输协会(IATA)预测,到2030年,全球城市空中交通市场规模将达到1.5万亿美元。在这一背景下,《低…

2026/11/5 11:34:12

链表数据结构与PTA解题技巧详解

1. 链表基础与数据结构课程实践 链表作为线性表的链式存储结构,是数据结构课程中承前启后的关键内容。在山东理工大学(SDUT)的数据结构与算法课程体系中,链表章节通常安排在顺序表之后、树结构之前,这种教学顺序设计体…

2026/11/5 11:34:12

Simulink S-Function核心机制与两个经典C示例解析

简介:本资源是面向MATLAB/Simulink初学者与控制系统建模学习者的S-Function入门实践包,聚焦自定义系统函数开发这一核心能力,解决标准库无法覆盖的特定数学运算与动态逻辑建模需求。压缩包共4个文件(2个M文件实现核心算法逻辑&…

2026/11/5 11:34:12

Mantle系统架构:云存储局部性与扩展性的突破

1. 项目概述:Mantle系统架构演进的技术突围在云存储技术高度成熟的今天,百度沧海存储团队完成的Mantle系统设计堪称一次教科书级别的技术突围。这个被SOSP25收录的存储系统创新案例,完美诠释了如何在看似饱和的技术领域实现突破——不是通过堆…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑