跳至主内容区

常见陷阱

[非官方测试版翻译]

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

性能优化建议

性能优化技巧请参阅性能指南

不要重新赋值草稿参数

切勿重新分配 draft 参数(例如:draft = myCoolNewState)。正确的做法是修改 draft 或返回新状态。详见从 producer 返回数据

Immer 仅支持单向树结构

Immer 要求状态必须是单向树结构。即树中不应出现重复对象,也不能存在循环引用。从根节点到任意子节点都应有且仅有一条路径。

避免在 producer 中显式返回 undefined

虽然可以从 producer 返回值,但返回 undefined 会导致问题——这会被误判为未修改草稿!如需将草稿替换为 undefined,请改用 nothing 作为返回值。

不要修改特殊对象

Immer 不支持特殊对象,例如 window.location。

类需特殊处理或避免修改

自定义类需要额外配置才能与 Immer 兼容。相关文档请参阅处理复杂对象章节。

数组仅支持索引和 length 属性修改

对于数组,只能修改数字索引属性和 length 属性。自定义属性不会被保留。

非源自状态的数据不会被草稿化

注意:即使数据最终成为新草稿的一部分,只要它源自闭包而非基础状态,就不会被草稿化。

function onReceiveTodo(todo) {
const nextTodos = produce(todos, draft => {
draft.todos[todo.id] = todo
// Note, because 'todo' is coming from external, and not from the 'draft',
// it isn't draft so the following modification affects the original todo!
draft.todos[todo.id].done = true

// The reason for this, is that it means that the behavior of the 2 lines above
// is equivalent to code, making this whole process more consistent
todo.done = true
draft.todos[todo.id] = todo
})
}

Immer 补丁不一定最优

Immer 生成的补丁集保证正确性——应用于相同基础对象会得到相同终态。但无法保证补丁集是最精简的(即可能包含冗余操作)。

务必使用嵌套 producer 的返回值

虽然支持嵌套 produce 调用,但请注意:produce 总会生成新状态。即使将草稿传入嵌套 produce,内部修改也不会反映到外层草稿中——这些变更仅存在于内层 produce 的返回值里。换句话说,嵌套使用时实际得到的是"草稿的草稿",内层结果必须合并回原始草稿(或直接返回)。例如 produce(state, draft => { produce(draft.user, userDraft => { userDraft.name += "!" })}) 无法生效,因为内层返回值未被使用。正确用法如下:

produce(state, draft => {
draft.user = produce(draft.user, userDraft => {
userDraft.name += "!"
})
})

草稿对象不具有引用相等性

Immer 中的草稿对象由 Proxy 封装,因此无法通过 ===== 比较原始对象与等效草稿(例如数组元素匹配时)。此时应使用 original 辅助方法:

const remove = produce((list, element) => {
const index = list.indexOf(element) // this won't work!
const index = original(list).indexOf(element) // do this instead
if (index > -1) list.splice(index, 1)
})

const values = [a, b, c]
remove(values, a)

建议尽可能在 produce 函数外进行比较,或使用 .id 等唯一标识属性,以避免依赖 original

数组方法插件:回调函数接收基础值

使用数组方法插件enableArrayMethods())时,被覆盖方法(如 filterfindsomeeveryslice)的回调函数接收的是基础值(而非草稿)。这是核心性能优化机制——避免在迭代时为每个元素创建代理。

import {enableArrayMethods, produce} from "immer"
enableArrayMethods()

produce(state, draft => {
draft.items.filter(item => {
// `item` is a base value here, NOT a draft
// Reading works fine:
return item.value > 10

// But direct mutation here won't be tracked:
// item.value = 999 // ❌ Won't affect the draft!
})

// Instead, use the returned result (which contains drafts):
const filtered = draft.items.filter(item => item.value > 10)
filtered[0].value = 999 // ✅ This works - filtered[0] is a draft
})

此规则仅适用于被插件拦截的方法。未被覆盖的方法(如 mapforEachreduce)仍保持常规行为——它们的回调函数接收草稿对象。