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: string
age: number
hobby: 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';
});