Table of Contents
核心概念
1. 三个关键角色
- Base State(基础状态):原始的不可变数据
- Draft State(草稿状态):可以被修改的代理对象
- Next State(下一个状态):基于修改生成的新的不可变数据
2. 基本使用方式
const newState = produce(baseState, (draft) => { // 在这里可以直接修改 draft draft.user.name = '新名字'})实现原理
简化版实现
基于项目中的示例代码,我们可以看到 Immer 的核心实现:
const produce = (base, fn) => { // 1. 用于存储所有修改的对象 const modified = {}
// 2. 创建 Proxy 处理器 const handler = { // 拦截读取操作 get(target, prop, receiver) { // 如果属性已被修改,返回修改后的值 if (prop in modified) { return modified[prop] } // 如果属性是对象,递归创建 Proxy if (typeof target[prop] === 'object' && target[prop] !== null) { return new Proxy(target[prop], handler) } // 否则返回原始值 return Reflect.get(target, prop, receiver) },
// 拦截写入操作 set(_target, prop, value) { // 将修改记录到 modified 对象中 return Reflect.set(modified, prop, value) }, }
// 3. 创建基础对象的代理 const proxy = new Proxy(base, handler)
// 4. 执行用户的修改函数 fn(proxy)
// 5. 如果没有修改,直接返回原对象 if (Object.keys(modified).length === 0) { return base }
// 6. 返回合并后的新对象 return JSON.parse(JSON.stringify(proxy))}工作流程详解
步骤 1:创建 Proxy
当调用 produce(obj, fn) 时,Immer 会为基础对象创建一个 Proxy 代理。
const obj = { user: { name: '张三', age: 18, },}
const newObj = produce(obj, (draft) => { // draft 是 obj 的 Proxy 代理})步骤 2:拦截读取操作(get)
当访问 draft.user.name 时:
- 首先检查
modified对象中是否有user属性 - 如果没有,检查
obj.user是否是对象 - 如果是对象,为
obj.user创建一个新的 Proxy - 这样就实现了深层嵌套对象的代理
draft.user.name// ↓get(obj, 'user', receiver)// → obj.user 是对象,返回 new Proxy(obj.user, handler)// ↓get(obj.user, 'name', receiver)// → 返回 '张三'步骤 3:拦截写入操作(set)
当执行 draft.user.name = '李四' 时:
- Proxy 的
set方法被触发 - 修改被记录到
modified对象中 - 原始的
obj不会被修改
draft.user.name = '李四'// ↓set(obj.user, 'name', '李四')// → modified.name = '李四'步骤 4:生成新状态
在用户的修改函数执行完毕后:
- 检查
modified对象是否为空 - 如果为空,说明没有任何修改,直接返回原对象(结构共享)
- 如果有修改,将
proxy序列化后反序列化,生成一个新对象
关键特性
1. 结构共享(Structural Sharing)
如果没有进行任何修改,Immer 会返回原始对象,而不是创建新对象:
const obj1 = { name: '张三' }const obj2 = produce(obj1, (draft) => { // 没有修改})
console.log(obj1 === obj2) // true2. 深层嵌套支持
通过递归创建 Proxy,Immer 可以处理任意深度的嵌套对象:
produce(state, (draft) => { draft.level1.level2.level3.value = 'new value'})3. 写时复制(Copy-on-Write)
只有在真正修改时才会创建新对象,未修改的部分保持原引用。
实际应用场景
在 Zustand 中使用 Immer
import { create } from 'zustand/react'import { immer } from 'zustand/middleware/immer'
interface Store { user: { name: string; age: number } updateName: (name: string) => void}
const useStore = create<Store>()( immer((set) => ({ user: { name: '张三', age: 18 }, updateName: (name) => set((state) => { // 可以直接修改 state,Immer 会处理不可变性 state.user.name = name }), })))在 React 状态管理中
const [state, setState] = useState({ user: { name: '张三' } })
// 使用 Immer 更新深层状态setState(produce((draft) => { draft.user.name = '李四'}))实际 Immer 库的优化
上面的简化实现只是为了演示原理,实际的 Immer 库还包括:
- 更高效的对象合并:不使用 JSON 序列化,而是智能地合并对象
- 支持数组操作:可以使用
push、splice等数组方法 - 支持 Map 和 Set:扩展了对 ES6 集合类型的支持
- 自动冻结:在开发环境中冻结生成的对象,防止意外修改
- 性能优化:使用多种技术优化性能,包括对象池、懒初始化等
优势与限制
优势
✅ 更直观的代码:可以像修改可变数据一样编写代码
✅ 自动不可变性:无需手动使用扩展运算符或 Object.assign
✅ 减少样板代码:特别是在处理深层嵌套数据时
✅ 结构共享:未修改的部分保持引用相等
限制
⚠️ 必须在 produce 函数内修改:在外部修改无效
⚠️ 不支持返回值:不能在 draft 函数中 return 新值(需要特殊处理)
⚠️ 性能开销:Proxy 有一定的性能开销,但通常可以忽略不计
总结
Immer 通过巧妙地使用 JavaScript Proxy 特性,实现了以可变方式编写代码、生成不可变数据的目标。其核心原理是:
- 使用 Proxy 拦截对象的读写操作
- 记录所有的修改操作
- 在修改完成后,生成一个新的不可变对象
- 通过结构共享优化性能
这使得在 React、Redux、Zustand 等需要不可变数据的场景中,可以更方便地管理状态。
评论 (0)
请先登录后再发表评论