CodeWalk

Immer 不可变数据原理(Proxy 与草稿状态)

作者:孤独的心 · 2026-05-30 12:55

请解释 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']

核心原理

  1. Proxy 代理

    • produce 函数创建一个 Proxy(draft, handler) 包裹 baseState
    • Proxy 拦截所有 get/set 操作
  2. 结构化共享(Structural Sharing)

    • 当访问 draft.items 时,Proxy 返回一个子 Proxy(懒初始化)
    • 当修改 draft.items.push('c') 时,Proxy 记录变更路径
    • 最终生成新对象时,对未修改的子树直接复用引用
  3. 冻结与变更跟踪

    • Immer 内部维护一个变更记录树
    • 每次 set 操作标记该路径为已修改
    • produce 完成后,根据变更树生成新对象
  4. 不变部分共享

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 时可以像操作可变数据一样工作,同时享受不可变数据的优势(时间旅行、浅比较优化)。