redux toolkit 内置 immer 通过 proxy 拦截 + 惰性克隆 + 差异合成实现高效不可变更新,仅克隆被修改的路径分支,复用未变更部分的原始引用,避免全量深拷贝,支持函数、symbol、循环引用等,时间复杂度接近 o(m)(m 为变更路径长度)。

Redux Toolkit 内置 Immer,它不靠“暴力全量深拷贝”实现不可变更新,而是用 Proxy 拦截 + 惰性克隆 + 差异合成的方式,只复制真正被修改的路径分支,其余部分复用原始引用。
Proxy 拦截写操作,生成 draft 代理对象
调用 produce(state, recipe) 时,Immer 会为 state 创建一个 Proxy(称为 draft),所有对 draft 的赋值、删除、新增操作都被 set/get 陷阱捕获。原始 state 完全不动,所有变更只记录在 draft 的元信息中(比如 modified: true、copy 字段等)。
- 未访问的嵌套属性不会触发代理创建(懒初始化),节省内存和性能
- 数组 push/pop、对象属性赋值、深层路径修改(如
draft.user.profile.city = "Beijing")全部被拦截并标记“已变更” - 没被 touch 过的子树,比如
state.user.token或state.settings.theme,后续直接复用原引用,不拷贝
只克隆变更路径上的节点,其他保持原引用
当 recipe 执行完毕,Immer 开始生成最终新状态:从根开始递归判断每个属性是否被修改过。只有被修改的分支才执行浅拷贝(Object.assign({}, base)),并向下继续检查;未修改的则直接返回原始值。
FastAPI + Flask 混合部署最佳实践,解决路由定义、API 代理等常见问题,适用于同时运行 FastAPI API 与 Flask 前端的场景。
- 例如修改
draft.items[2].price,Immer 只会克隆 items 数组(浅拷贝)、items[2] 对象(浅拷贝),而 items[0] 和 items[1] 仍指向原对象 - Date、RegExp、Map、Set 等类型若未被修改,也原样保留;若被赋新值(如
draft.lastLogin = new Date()),则新值直接写入,无需特殊序列化 - 整个过程不依赖
JSON.stringify,因此函数、undefined、Symbol、循环引用都能正确处理
结构共享带来实际性能提升
这种“按需克隆”机制让 Immer 在多数场景下比传统深拷贝快数倍,尤其适合 Redux 中频繁局部更新的场景——90% 的 reducer 更新只影响一两个字段,却不必遍历整个 state 树。
- state 越深、越宽,优势越明显;全量深拷贝时间复杂度是 O(n),Immer 接近 O(m),m 是变更路径长度
- RTK 的
createSlice默认使用 produce,你写的 reducer 里看似在“改 draft”,实际输出的是全新不可变对象,且自动优化了引用关系 - 开发者无需手动写
{...state, user: {...state.user, profile: {...state.user.profile, city: "Beijing"}}}这类易错长链展开
注意边界:自定义类和原型链需显式支持
Immer 默认只代理 plain object、array、Map、Set 等可冻结/可遍历结构。如果 state 中含自定义类实例(如 new User()),且希望其内部属性也被代理,需在类上设置 immerable = true 静态属性,否则会被跳过代理,当作普通对象处理。
- 不加
immerable时,draft.user.name = "Bob"不会触发代理,而是直接赋值到原实例 —— 违反不可变原则 - 加上后,Immer 才会为其创建 draft 代理,并在产出时生成新实例(需配合自定义
clone方法或构造函数重建) - 普通 JS 对象、数组、内置集合类型无需额外配置,开箱即用










