学习React-8-useImmer
useImmer
useImmer
是基于 immer 库实现的一个 React Hook,它让你可以像修改可变数据一样来修改不可变数据。immer 是一个不可变的数据结构库,完全符合 React 的不可变性原则。
特点
- 可变式写法:你可以像修改可变数据一样编写代码,但实际上是在生成新的不可变数据
- 类型安全:完全支持 TypeScript,提供良好的类型推断
- 性能优化:使用结构共享,只修改变化的部分,其他部分保持引用不变
实现原理
immer 的核心是使用 Proxy 对象来拦截对原始数据的修改操作。当你在 produce 函数中修改数据时,immer 会记录这些修改,然后基于原始数据生成一个新的不可变对象。
使用
因为这并不是React官方的包,所以需要下载 npm install immer use-immer
小栗子
useImmer
import React from 'react';
import { useImmer, useImmerReducer } from 'use-immer';interface User {name: stringage: numberhobby: Array<string>}
interface UseImmerProps {pUser: User;
}const UseImmer: React.FC<UseImmerProps> = (props) => {const [user, updateUser] = useImmer<User>(props.pUser);const handleIncreaseAge = () => {updateUser(draft => {draft.age += 1;});};// 操作对象的值const handleSetName = (name: string) => {updateUser(draft => {draft.name = name;});};// 操作数组的值const handleHobby = (name: string) => {updateUser(draft => {draft.hobby.push(name)})}return (<div><p>User: {user.name}, Age: {user.age}</p>{user.hobby.map((item, index) => {return <p key={`${item}_${index}`}>{item}</p>;})}<button onClick={handleIncreaseAge}>Increase Age</button><button onClick={() => handleSetName('黑子')}>Change name</button><button onClick={() => handleHobby('跳舞')}>add hobby</button></div>);
};export default UseImmer;// App调用
<UseImmer pUser={{name: 'kun', age: 18, hobby: ['唱歌'] }}></UseImmer>
useImmerReducer
interface State {count: number;
}
type Action = | { type: 'INCREMENT' }| { type: 'DECREMENT' }const initState = {count: 0
}
const counterReducer = (state: State, action: Action) => {switch (action.type) {case 'INCREMENT':state.count += 1;break;case 'DECREMENT':state.count -= 1;break;}
}
const UseImmer: React.FC<UseImmerProps> = (props) => {const [state, dispatch] = useImmerReducer(counterReducer, initState)return (<div><button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button><p>Count: {state.count}</p><button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button></div>);
};export default UseImmer;
useState和useImmer对比
维度 | useState(原生) | useImmer(基于 Immer) |
---|---|---|
引入成本 | React 自带 | 需安装 use-immer |
基本类型 | ✅ 直接 setCount(c => c + 1) | ✅ 同样写法 |
简单对象 / 数组 | ❗ 手动展开、拼接、拷贝 | ✅ 直接 draft.x.y = z |
深层嵌套结构 | ❌ 层层解构,代码冗长易错 | ✅ 按可变方式书写即可 |
不可变性保证 | 开发者完全自控 | Immer 内部自动生成新引用 |
样板代码量 | 多 | 少 |
调试可读性 | reducer / switch 可读性一般 | 逻辑扁平,更具命令式直觉 |
性能(简单场景) | 最快 | 稍慢(Immer Proxy 开销) |
性能(复杂场景) | 可能因多次浅拷贝浪费 | Immer 结构共享,反而更优 |
代码对比
useState —— 更新深层字段:
setState(prev => ({...prev,user: {...prev.user,address: {...prev.user.address,city: 'Shanghai'}}
}));
useImmer —— 同一段逻辑:
updateState(draft => {draft.user.address.city = 'Shanghai';
});