Immer
Immer
是一个用于管理不可变数据的JavaScript库,核心作用是让你用"可变"的写法来安全的操作不可变对象或数组,极大简化了复杂数据结构的更新逻辑。避免了手动深拷贝或扩展运算符的繁琐操作
核心原理
Immer
的核心机制基于"草稿状态(Draft State
)"和Proxy
对象
- 草稿状态 :用户在一个函数内直接修改草稿对象,
Immer
会记录所有变更 - 不可变转换 :
Immer
会根据草稿状态生成一个新的不可变对象,原始数据保持不变 Proxy
支持:现代浏览器中使用Proxy
追踪修改;旧环境(如IE)降级到ES5
的Object.defineProperty
使用场景
React
状态管理:简化useState
或useReducer
的复杂状态更新
js-useState
import produce from 'immer'
const [user, setUser] = useState({name: '张三', info: { age: 18 }})
const changeAge = () => {
setState(produce(draft) => {
draft.info.age = 20
})
}
js-useReducer
import produce from 'immer'
import { useReducer } from 'react'
function reducer(state, action){
return produce(state, draft => {
switch(action.type){
case 'add':
draft.list.push(action.payload)
break;
case 'update':
draft.user.name = action.payload
break;
// ....
}
})
}
const [state, dispatch] = useReducer(reducer, { list: [], user: { name: '' } })
Redux Reducers
:
在Redux
中,reducer
函数需要返回一个新的状态对象。使用Immer
可以避免手动编写复杂的不可变更新逻辑;Redux Toolkit
(RTK)内置了Immer
,createSlice
里的reducer
可以直接用"可变写法"
js
import produce from 'immer'
const initialState = {
list: [],
user: { name: '', age: 0 }
}
function reducer(state = initialState, action){
return produce(state, draft => {
switch(action.type){
case 'ADD_ITEM':
draft.list.push(action.payload)
break;
case 'SET_USER_NAME':
draft.user.name = action.payload
break
// ....
}
})
}
复杂数据结构的更新:处理嵌套对象或数组
js
const state = {
user: {
name: 'Alice',
address: {
city: 'Wonderland',
zip: '123'
}
}
}
const nextState = produce(state, draft => {
draft.user.address,city = 'Newland'
})
性能优化
Immer
通过结构共享来优化性能,它只会复制被修改的部分数据,而未修改的部分会直接引用原始数据 。这种方式在大型数据集上可以显著减少内存占用和计算开销
use-immer
use-immer
是一个基于Immer
的React Hook,用于简化React组件中复杂状态(如嵌套对象、数组等)的不可变更新。它结合了Immer
的不可变更新能力和React的useState
,允许开发者以"可变"语法更新状态,同时自动生成新的不可变状态
使用场景
- 需要频繁操作数组、对象、嵌套结构(如表单、树形数据、列表等)
- 需要保证状态状态不可变(如Redux、React状态),但又想写法简单
- 需要避免手动深拷贝、展开对象的繁琐和易错
使用方法
- 安装:
npm install immer use-immer
- 基本示例
js
import React from 'react'
import { useImmer } from 'use-immer'
function App(){
// 类似 useState ,但返回的 update 函数 支持 Immer 的草稿更新
const [user, updateUser] = useImmer({
name: 'Alice',
profile: { age: 28, hobbies: [ 'coding', 'music' ] }
})
const handleAgeIncrease = () => {
updateUser((draft) => {
draft.profile.age += 1
})
}
const addHobby = () => {
updateUser((draft) => {
draft.profile.hobbies.push('reading')
})
}
return (
<div>
<p>Name: {user.name}, Age: {user.profile.age}</p>
<button onClick={handleAgeIncrease}>Increase Age</button>
<button onClick={addHobby}>Add Hobby</button>
</div>
)
}
进阶用法
- 与
Immer
的produce
结合
js
// 当需要复用状态更新逻辑时,可分离出纯函数
const updateProfile = produce(draft => {
draft.profile.age += 1;
})
// 在组件使用
updateUser(updateProfile)
- 处理数组:直接使用数组方法(无需创建新数组)
js
const [ todos, updateTodos ] = useImmer([
{ id: 1, text: 'Learn React', done: false }
])
// 添加任务
updateTodos((draft) => {
draft.push( { id: 2, text: 'Learn Immer', done: false } )
})
// 标记完成
updateTodos((draft) => {
const todo = draft.find(item => item.id === 1)
if(todo) todo.done = true
})
TypeScript
支持:提供完整的类型推断,无需额外定义类型
js
interface User{
name: string,
profile: {
age: number,
hobbies: string[]
}
}
const [ user, updateUser ] = useImmer<User>({
name: 'KDD',
profile: { age: 19, hobbies: [] }
})
- 结合
UseContext
js
import React, { createContext, useContext } from 'react'
import { useImmer } from 'use-immer'
const StateContext = createContext()
function App(){
const [ state, updateState] = useImmer({
theme: 'light',
user: {
name: 'Alice',
age: 25
}
})
return (
<StateContext.Provider value={{state, updateState}}>
<ChildComponent />
</StateContext.Provider>
)
}
function ChildComponent(){
const {state, updateState} = useContext(StateContext)
const toggleTheme = () => {
updateState(draft => {
draft.theme = draft.theme === 'light' ? 'dark' : 'light'
})
}
return (
<div>
<p>Current Theme: { state.theme }</p>
<button onClick={toggleTheme}>Toggle Theme</button>
</div>
)
}
use-immer
和useState
的区别
状态管理的方式
useState
:是React提供的一个基础Hook,用于在函数组件中管理状态,它返回一个状态值和一个更新该状态的函数。每次状态更新时,组件会重新渲染use-immer
:是基于immer
库的Hook,允许以更直接的方式更新状态。Immer通过使用不可变数据结构 ,使得可以直接修改状态的副本,而不需要手动创建新的状态对象。
更新状态的语法
useState
:
js
const [state, setState] = useState({count: 1})
setState({count: state.count + 1})
use-immer
js
const [state, updateState] = useImmer({ count: 0 });
updateState(draft => {
draft.count += 1
})
复杂状态的处理
useState
:
js
const [state, setState] = useState({ user: { name: 'John', age: 30 } });
setState(prevState => {
...prevState,
user: {
...prevState.user,
age: prevState.user.age + 1
}
})
use-immer
js
const [state, updateState] = useImmer({ user: { name: 'John', age: 30 } });
updateState(draft => {
draft.user.age += 1
})
性能考虑
useState
在每次状态更新时都会触发组件的重新渲染,适用于简单的状态管理场景use-immer
由于使用了immer
的不可变数据结构,可能会在复杂状态更新时带来额外的性能开销 ,但在处理复杂状态时提供了更好的开发体验。
适用场景
useState
适用于简单的状态管理,尤其是当状态结构较为扁平时use-immer
更适合处理复杂的状态对象或嵌套结构 ,尤其是在需要频繁更新深层属性时,能够显著减少代码复杂度。