状态处理
在上一章我们讲了,Zustand会合并第一层的state,但是如果深层次应该如何处理呢
来吧演示
首先创建一个葫芦娃,葫芦娃有七个娃,每个娃都有自己的状态,我们可以通过updateGourd来更新葫芦娃的状态,这样就实现了一个深层次的demo
ts
import { create } from 'zustand'
interface User {
gourd: {
oneChild: string,
twoChild: string,
threeChild: string,
fourChild: string,
fiveChild: string,
sixChild: string,
sevenChild: string,
},
updateGourd: () => void
}
const useUserStore = create<User>(((set) => ({
//创建葫芦娃
gourd: {
oneChild: '大娃',
twoChild: '二娃',
threeChild: '三娃',
fourChild: '四娃',
fiveChild: '五娃',
sixChild: '六娃',
sevenChild: '七娃',
},
updateGourd: () => set((state) => ({
gourd: {
//...state.gourd, 先不进行状态合并 // [!code highlight]
oneChild: '大娃-超进化',
}
}))
})))
export default useUserStore;
我们会发现如果不进行状态合并,其他的状态是会丢失的,所以深层次的状态处理需要进行状态合并,但是如果代码过多,每次都需要合并状态也挺烦的,所以我们可以通过immer
中间件处理这个问题

使用immer中间件
安装
bash
npm install immer
原始immer
的用法
需要导出produce,然后它的第一个参数是原始值,第二个参数是一个回调函数,回调函数中的参数是draft,也就是原始值的拷贝,然后我们就可以直接修改draft了,最后返回新的值
ts
import { produce } from 'immer'
const data = {
user: {
name: '张三',
age: 18
}
}
const newData = produce(data, draft => {
draft.user.age = 20
})
console.log(newData,data)
//{ user: { name: '张三', age: 20 } }
//{ user: { name: '张三', age: 18 } }
immer
在Zustand
中的使用方法
引入注意是从zustand/middleware/immer
引入,而不是immer
ts
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
- 首先从zustand中间件引入
immer
- 然后注意结构
create()(immer())
这里是两个括号而不是放在create里面了
ts
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface User {
gourd: {
oneChild: string,
twoChild: string,
threeChild: string,
fourChild: string,
fiveChild: string,
sixChild: string,
sevenChild: string,
},
updateGourd: () => void
}
//注意结构发生了变化!!!
const useUserStore = create<User>()(immer(((set) => ({
//创建葫芦娃
gourd: {
oneChild: '大娃',
twoChild: '二娃',
threeChild: '三娃',
fourChild: '四娃',
fiveChild: '五娃',
sixChild: '六娃',
sevenChild: '七娃',
},
updateGourd: () => set((state) => {
state.gourd.oneChild = '大娃-超进化' //这儿不需要状态合并了需要修改什么值直接改就行了
state.gourd.twoChild = '二娃-谁来了'
state.gourd.threeChild = '三娃-我来了'
})
}))))
export default useUserStore;
immer原理剖析
immer.js 通过 Proxy 代理对象的所有操作,实现不可变数据的更新。当对数据进行修改时,immer 会创建一个被修改对象的副本,并在副本上进行修改,最后返回修改后的新对象,而原始对象保持不变。这种机制确保了数据的不可变性,同时提供了直观的修改方式。
immer 的核心原理基于以下两个概念:
- 写时复制 (Copy-on-Write)
- 无修改时:直接返回原对象
- 有修改时:创建新对象
- 惰性代理 (Lazy Proxy)
- 按需创建代理
- 通过 Proxy 拦截操作
- 延迟代理创建
工作流程
graph TD
A[调用 produce] -->
B[创建代理 draft] -->
C[执行 recipe 修改 draft] -->
D[是否有修改!]
D-- 是 --> E[创建新对象...base + ...modified]
D-- 否 --> F[直接返回 base]
简化实现
ts
type Draft<T> = {
-readonly [P in keyof T]: T[P];
};
function produce<T>(base: T, recipe: (draft: Draft<T>) => void): T {
// 用于存储修改过的对象
const modified: Record<string, any> = {};
const handler = {
get(target: any, prop: string) {
// 如果这个对象已经被修改过,返回修改后的对象
if (prop in modified) {
return modified[prop];
}
// 如果访问的是对象,则递归创建代理
if (typeof target[prop] === 'object' && target[prop] !== null) {
return new Proxy(target[prop], handler);
}
return target[prop];
},
set(target: any, prop: string, value: any) {
// 记录修改
modified[prop] = value;
return true;
}
};
// 创建代理对象
const proxy = new Proxy(base, handler);
// 执行修改函数
recipe(proxy);
// 如果没有修改,直接返回原对象
if (Object.keys(modified).length === 0) {
return base;
}
// 创建新对象,只复制修改过的属性
return {
...base,
...modified
};
}
// 使用示例
const state = {
user: {
name: '张三',
age: 25
}
};
const newState = produce(state, draft => {
draft.user.name = '李四';
draft.user.age = 26;
});
console.log(state); // { user: { name: '张三', age: 25 } }
console.log(newState); // { user: { name: '李四', age: 26 } }
这儿只是简单实现,没有考虑数组的情况和深层次的代理,只实现了其核心思想