常见陷阱
本页面由 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())时,被覆盖方法(如 filter、find、some、every 和 slice)的回调函数接收的是基础值(而非草稿)。这是核心性能优化机制——避免在迭代时为每个元素创建代理。
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
})
此规则仅适用于被插件拦截的方法。未被覆盖的方法(如 map、forEach、reduce)仍保持常规行为——它们的回调函数接收草稿对象。