Immer 不可变数据原理(Proxy 与草稿状态)
请解释 Immer 库如何通过 Proxy 实现不可变数据(Immutability),使用 produce 时 draft 状态的工作原理,以及为什么 mutate draft 不会改变原始对象。
回答
孤独的心
Immer 工作原理:
import { produce } from 'immer';
const baseState = { count: 0, items: ['a', 'b'] };
const nextState = produce(baseState, (draft) => {
draft.count = 1;
draft.items.push('c');
});
console.log(baseState.count); // 0(未修改)
console.log(nextState.count); // 1(新对象)
console.log(baseState.items); // ['a', 'b'](未修改)
console.log(nextState.items); // ['a', 'b', 'c']
核心原理:
-
Proxy 代理:
produce函数创建一个Proxy(draft, handler)包裹 baseState- Proxy 拦截所有 get/set 操作
-
结构化共享(Structural Sharing):
- 当访问
draft.items时,Proxy 返回一个子 Proxy(懒初始化) - 当修改
draft.items.push('c')时,Proxy 记录变更路径 - 最终生成新对象时,对未修改的子树直接复用引用
- 当访问
-
冻结与变更跟踪:
- Immer 内部维护一个变更记录树
- 每次 set 操作标记该路径为已修改
produce完成后,根据变更树生成新对象
-
不变部分共享:
const nextState = produce(currentState, draft => {
draft.a.b.c = 1; // 只修改了 a.b.c 路径
});
// nextState.a === currentState.a? 否(a 变了)
// nextState.a.b === currentState.a.b? 否(b 变了)
// nextState.a.b.c === 1(新值)
// nextState.x === currentState.x? 是(未改,共享引用)
优势:写 reducer 时可以像操作可变数据一样工作,同时享受不可变数据的优势(时间旅行、浅比较优化)。