Oasis's Cloud

一个人的首要责任,就是要有雄心。雄心是一种高尚的激情,它可以采取多种合理的形式。
—— 《一个数学家的辩白》

Immer 数据处理流程解析

作者:oasis


Immer 通过 produce 函数实现不可变数据的处理。整个流程可以概括为:创建 draft → 修改跟踪 → 最终化处理 → 返回新状态。下面结合流程图详细说明每个阶段的工作机制。

img.png

流程概览

从流程图中可以看到,Immer 的数据处理主要分为三个阶段:

  1. 初始化阶段:创建 scope 和根 draft
  2. 修改阶段:通过 Proxy 拦截操作,跟踪修改
  3. 最终化阶段:根据修改情况生成新状态

初始化阶段

当调用 produce(baseState, draft => { ... }) 时,首先会创建一个新的 scope:

const scope = enterScope(this)
const proxy = createProxy(base, undefined)

Scope 的作用: - 管理整个 produce 调用的上下文 - 存储所有创建的 draft 对象 - 提供错误恢复机制

Draft 的创建: - 为原始对象创建 Proxy 代理 - 每个 draft 内部都有 DRAFT_STATE 属性 - State 包含 base_(原始对象)、copy_(null)、modified_(false)等

修改阶段

当在 recipe 函数中修改 draft 时,Proxy 会拦截这些操作:

属性访问

draft.user.age = 31

访问 draft.user 时: - 检查 user 属性是否等于原始值 - 如果相等,调用 prepareCopy(state) 准备副本 - 创建 user 对象的 draft 并返回

属性赋值

设置 age 属性时: - prepareCopy(state):为 draft 创建 copy_(浅拷贝) - markChanged(state):标记当前 state 为 modified_ = true - 递归向上标记所有父级 state 为已修改 - 将新值赋给 copy_ 中的对应属性

关键设计: - 惰性复制:只有在真正修改时才创建副本 - 修改传播:子对象的修改会自动传播到父级 - 结构共享:未修改的部分保持引用共享

最终化阶段

当 recipe 函数执行完成后,进入最终化处理:

processResult(result, scope)

最终化流程

  1. 检查修改状态

    • 如果 modified_ = false,直接返回原始对象
    • 如果 modified_ = true,处理 copy_
  2. 递归处理子属性

    • 遍历 copy_ 中的所有属性
    • 如果属性是 draft,递归调用 finalize
    • 如果属性是普通对象,根据配置决定是否冻结
  3. 生成补丁(如果启用):

    • 收集所有变更操作
    • 生成 patches_inversePatches_
  4. 清理资源

    • 撤销所有 draft 对象
    • 释放 scope 资源

示例分析

const baseState = {
  user: {
    name: "John",
    age: 30,
    address: {
      city: "New York",
      country: "USA"
    }
  },
  todos: ["task1", "task2"]
};

const nextState = produce(baseState, draft => {
  draft.user.age = 31;
  draft.todos.push("task3");
});

执行流程

  1. 初始化

    • 创建 scope
    • 创建根 draft(baseState 的代理)
  2. 修改 draft.user.age

    • 访问 draft.user → 创建 user draft
    • 设置 age → 标记 user state 和根 state 为已修改
    • 创建 user 的 copy_,修改 age 为 31
  3. 修改 draft.todos

    • 访问 draft.todos → 创建数组 draft
    • push 操作 → 标记数组 state 和根 state 为已修改
    • 创建数组的 copy_,添加 “task3”
  4. 最终化

    • 根 state 被修改,返回新的根对象
    • user 被修改,返回新的 user 对象(age=31)
    • address 未修改,保持原始引用
    • todos 被修改,返回新的数组

结果

console.log(baseState.user === nextState.user); // false
console.log(baseState.user.address === nextState.user.address); // true
console.log(baseState.todos === nextState.todos); // false

性能优化

Immer 通过以下机制优化性能:

  1. Copy-on-Write:只在修改时创建副本
  2. 结构共享:未修改的部分保持引用
  3. 修改跟踪:避免不必要的深度遍历
  4. 批量处理:scope 统一管理所有 drafts

这种设计使得 Immer 既能保证不可变性,又能在性能上接近直接修改的方式。