1109 字
3 分钟
useImmer

useImmer
useImmer 是 Immer 库为 React 提供的自定义 Hook,它基于 Immer 核心的 “可变式操作不可变数据” 理念,让开发者可以以直观的、可变的方式修改状态,同时底层自动生成不可变的新状态,无需手动解构或拼接对象/数组,大幅简化 React 中复杂状态的更新逻辑。
Immer 本身是由 React 核心团队成员开发的库,useImmer 是其针对 React 状态管理场景的专属封装,兼容 React 函数组件的 Hook 规范,常作为 useState 的替代方案用于处理嵌套/复杂结构的状态。
useImmer的核心作用
- 简化不可变状态更新:解决 React 中使用
useState更新嵌套对象/数组时,需要手动展开(如...扩展运算符)、逐层复制的繁琐问题,通过 “草稿(draft)” 对象的可变操作,自动生成不可变的新状态。 - 保持状态不可变性:开发者操作的是草稿对象(临时可变副本),原状态不会被直接修改,最终输出的新状态仍符合 React 不可变数据的设计原则,避免因直接修改状态导致的渲染异常。
- 降低代码复杂度:对于多层嵌套的状态(如
{ user: { info: { address: [...] } } }),无需嵌套的扩展运算符,只需像操作普通可变对象一样修改草稿,大幅提升代码可读性和可维护性。 - 兼容
useState用法:useImmer的基础用法与useState高度一致,学习成本低,可平滑替换,同时支持函数式更新逻辑。
useImmer的使用方法
第一步:安装依赖
首先需安装 Immer 库(useImmer 包含在 immer 包中):
npm install immer --save# 或yarn add immer第二步:基础用法
import { useImmer } from 'immer';
function App() { // 初始化状态,用法与 useState 一致 const [state, setState] = useImmer({ name: '张三', age: 20, hobbies: ['读书', '运动'] });
// 修改状态:通过可变方式操作 draft 草稿对象 const updateUser = () => { setState((draft) => { // 直接修改草稿对象,无需手动复制/解构 draft.age += 1; draft.hobbies.push('编程'); // 数组直接 push,无需 [...hobbies, '编程'] draft.name = '李四'; }); };
return ( <div> <p>姓名:{state.name}</p> <p>年龄:{state.age}</p> <p>爱好:{state.hobbies.join(', ')}</p> <button onClick={updateUser}>更新信息</button> </div> );}第三步:嵌套状态更新(核心优势场景)
import { useImmer } from 'immer';
function App() { const [user, setUser] = useImmer({ info: { basic: { name: '张三', address: { province: '北京', city: '朝阳区' } }, scores: { math: 90, english: 85 } } });
const updateAddress = () => { setUser((draft) => { // 直接修改嵌套层级的属性,无需多层扩展运算符 draft.info.basic.address.city = '海淀区'; draft.info.scores.math = 95; }); };
return ( <div> <p>城市:{user.info.basic.address.city}</p> <p>数学成绩:{user.info.scores.math}</p> <button onClick={updateAddress}>更新地址和成绩</button> </div> );}第四步:替换式更新(非函数式)
与 useState 类似,也可直接传入新状态(但失去 Immer 核心优势,仅兼容场景):
setUser({ ...user, info: { ...user.info, basic: { ...user.info.basic, name: '王五' } }});useImmer的使用案例
案例1:表单状态管理
处理多字段、嵌套结构的表单时,简化状态更新:
import { useImmer } from 'immer';
function UserForm() { const [form, setForm] = useImmer({ basic: { username: '', email: '', phone: '' }, preferences: { receiveEmail: true, theme: 'light' }, skills: [] });
// 输入框 onChange 处理 const handleInputChange = (field, value) => { setForm((draft) => { // 动态修改不同层级的表单字段 const [level1, level2] = field.split('.'); if (level2) { draft[level1][level2] = value; } else { draft[level1] = value; } }); };
// 添加技能 const addSkill = (skill) => { setForm((draft) => { draft.skills.push(skill); }); };
return ( <div> <input placeholder="用户名" value={form.basic.username} onChange={(e) => handleInputChange('basic.username', e.target.value)} /> <input placeholder="邮箱" value={form.basic.email} onChange={(e) => handleInputChange('basic.email', e.target.value)} /> <button onClick={() => addSkill('React')}>添加React技能</button> <button onClick={() => setForm((d) => (d.preferences.theme = 'dark'))}>切换深色主题</button> </div> );}案例2:列表数据操作(增删改)
管理表格/列表类数据时,简化行数据的修改、删除:
import { useImmer } from 'immer';
function TodoList() { const [todos, setTodos] = useImmer([ { id: 1, text: '学习React', completed: false }, { id: 2, text: '掌握useImmer', completed: false } ]);
// 切换待办完成状态 const toggleTodo = (id) => { setTodos((draft) => { const todo = draft.find((t) => t.id === id); if (todo) todo.completed = !todo.completed; }); };
// 删除待办 const deleteTodo = (id) => { setTodos((draft) => { const index = draft.findIndex((t) => t.id === id); if (index !== -1) draft.splice(index, 1); // 直接 splice,无需 filter }); };
// 添加待办 const addTodo = (text) => { setTodos((draft) => { draft.push({ id: Date.now(), text, completed: false }); }); };
return ( <div> <button onClick={() => addTodo('新的待办事项')}>添加待办</button> <ul> {todos.map((todo) => ( <li key={todo.id}> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> <button onClick={() => toggleTodo(todo.id)}>切换状态</button> <button onClick={() => deleteTodo(todo.id)}>删除</button> </li> ))} </ul> </div> );}useImmer的使用注意事项
- 依赖安装:需确保安装
immer包(版本 >= 9.0.0 推荐),低版本可能存在兼容性问题,且useImmer需从immer包中导入(而非单独的react-immer,该包已废弃)。 - 草稿对象仅在 setState 回调内有效:
draft是临时对象,仅能在setState(draft => { ... })的回调函数内操作,不可将其赋值给外部变量或异步使用,否则会导致状态更新异常。// 错误示例let tempDraft;setState((draft) => {tempDraft = draft;draft.name = '张三';});// 异步操作中使用 tempDraft 无效,且会破坏不可变性setTimeout(() => {tempDraft.age = 30; // 无任何效果,且可能引发内存问题}, 1000); - 避免直接返回新对象:在
setState回调中,若直接返回新对象(而非修改 draft),Immer 会退化为普通useState行为,失去其核心价值:// 不推荐(失去 Immer 优势)setState((draft) => {return { ...draft, age: 30 };});// 推荐setState((draft) => {draft.age = 30;}); - 数组操作注意事项:对数组 draft 使用
pop/push/splice/sort/reverse等可变方法是安全的(Immer 会追踪修改),但需注意:sort和reverse会直接修改草稿数组的顺序,若需保留原数组顺序,可先复制再操作(但 Immer 场景下无需额外处理)。 - 与 React 并发模式兼容:Immer 及
useImmer完全兼容 React 并发渲染(Concurrent Mode),状态更新逻辑不会因并发渲染导致数据不一致。 - 性能考量:
useImmer的性能与手动使用扩展运算符的useState接近,甚至在复杂嵌套状态下更优(因 Immer 内部采用结构化共享);但避免在每次渲染时创建大量临时状态,建议将状态更新逻辑抽离为函数。 - 不可修改原状态:即使使用
useImmer,也不可直接修改state本身(如state.age = 30),必须通过setState操作 draft 对象,否则会违反 React 不可变状态原则,导致组件不重新渲染。 - TypeScript 支持:使用 TypeScript 时,
useImmer可自动推导状态类型,无需额外定义;若需手动指定类型,可通过泛型:interface User {name: string;age: number;}const [user, setUser] = useImmer<User>({ name: '张三', age: 20 });
分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分信息可能已经过时
相关文章 智能推荐
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
.webp)
