
它从哪来
Immer 是第三方库。React 官方只在 useReducer 参考页里把它当作简化不可变更新的可选方案提了一句,给的例子用的是 useImmerReducer。
React 侧的两个 Hook 来自 use-immer 包,它自身依赖 immer:
npm install use-immerimport { useImmer } from 'use-immer'不要写成 import { useImmer } from 'immer'。immer 包里没有这个 Hook,这是抄配置时最常见的一处错。
它解决的啰嗦
用 useState 改一个嵌套三层的字段:
setUser(prev => ({ ...prev, info: { ...prev.info, basic: { ...prev.info.basic, city: '海淀区' }, },}))同样的改动用 useImmer:
setUser(draft => { draft.info.basic.city = '海淀区'})传进回调的是草稿,写法像直接改对象;Immer 在背后算出一份新的不可变状态。
基本用法
import { useImmer } from 'use-immer'
function App() { const [user, setUser] = useImmer({ name: '张三', hobbies: ['读书'], })
function update() { setUser(draft => { draft.name = '李四' draft.hobbies.push('编程') // 直接 push,不用 [...hobbies, '编程'] }) }
return ( <div> <p>{user.name}</p> <p>{user.hobbies.join('、')}</p> <button onClick={update}>更新</button> </div> )}签名与 useState 一致:返回 [state, updateState],updateState 同样接受函数形式。
列表的增删改
setTodos(draft => { const todo = draft.find(t => t.id === id) if (todo) todo.completed = !todo.completed})
setTodos(draft => { const index = draft.findIndex(t => t.id === id) if (index !== -1) draft.splice(index, 1)})
setTodos(draft => { draft.push({ id: Date.now(), text, completed: false })})push、splice、sort 这些可变方法在草稿上都是安全的。
四条边界
草稿只在回调执行期间有效。别把它存到外部变量,也别留给异步逻辑用。
回调里不要 return 值。这个箭头函数写法是错的:
// 错:返回了赋值结果,Immer 会把它当成新状态setForm(draft => (draft.theme = 'dark'))
// 对:写成块体setForm(draft => { draft.theme = 'dark'})原状态仍然不能直接改。user.name = '李四' 一样不触发重渲染,必须通过 setUser。
只有一层、字段不多的状态不值得用它。{ ...prev, age: 21 } 已经够短,为此多引一个依赖不划算。嵌套三层以上,或者要在 reducer 里反复改数组,收益才明显。
配合 reducer
需要 reducer 时用同包的 useImmerReducer,官方给的就是这个,用法与 useReducer 相同,只是 draft 可以直接改。
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 190 天
部分内容可能已过时