Zustand,作为轻量、高效的状态管理库,API 简洁直观,组合中间件、支持可变写法,同时保持性能和可维护性。比起 Redux,心智负担低,入门快,非常适合现代前端开发。
资料汇总参考来源:小满zs,immer官网,Zustand官网
01 - 快速入门
// 官网例子的补充
import { create } from "zustand";
// 状态接口
interface BearState {
bears: number;
}
// 动作接口
interface BearActions {
increasePopulation: () => void;
removeAllBears: () => void;
updateBears: (newBears: number) => void;
getBears: () => number; // getter 类型
}
// store 类型
export type BearStore = BearState & BearActions;
// 创建 store
const useBear = create<BearStore>((set, get) => ({
bears: 0,
increasePopulation: () =>
set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears: number) => set({ bears: newBears }),
getBears: () => get().bears, // 每次调用获取最新值
}));
export default useBear;
02 - 状态处理(使用immer库)
Zustand 的“浅合并(shallow merge)机制”
set()只会对第一层做浅合并,不会对嵌套对象做深合并,需要使用immer库来实现。
"浅合并"带来的问题
import { create } from "zustand";
interface User {
gourd: {
oneChild: string;
twoChild: string;
threeChild: string;
};
updateGourd: () => void;
}
const useUserStore = create<User>((set) => ({
gourd: {
oneChild: "大娃",
twoChild: "二娃",
threeChild: "三娃",
},
// ❌ 这种写法会丢失 twoChild 和 threeChild 的数据
// updateGourd: () =>
// set((state) => ({
// gourd: {
// oneChild: "大娃-超进化",
// },
// })),
// ❌ 对的,但增加心智负担,不好维护
updateGourd: () =>
set((state) => ({
gourd: {
...state.gourd,
oneChild: "大娃-超进化",
},
})),
}));
export default useUserStore;
在Zustand中immer库
import { create } from "zustand"
import { immer } from "zustand/middleware/immer"
const useUserStore = create<User>()(
immer((set) => ({
gourd: {
oneChild: "大娃",
twoChild: "二娃",
threeChild: "三娃",
},
// ✅ 直接修改状态,无需手动合并
updateGourd: () =>
set((state) => {
state.gourd.oneChild = "大娃-超进化"
state.gourd.threeChild = "三娃-我来了"
}),
}))
)
immer原理解析
让你用“可变写法”去安全地修改不可变数据,本质上是简化不可变数据的更新。
immer 的核心原理基于两大概念:写时复制,惰性代理。
immer.js 通过 Proxy 代理对象的所有操作,实现不可变数据的更新。当对数据进行修改时,immer 会创建一个被修改对象的副本,并在副本上进行修改,最后返回修改后的新对象,而原始对象保持不变。这种机制确保了数据的不可变性,同时提供了直观的修改方式。
基本使用
import { produce } from "immer";
const todos = [
{ title: "Learn JS", done: false },
{ title: "Try Immer", done: false },
];
const nextTodos = produce(todos, (draft) => {
draft.push({ title: "Subscribe", done: false });
draft[0].done = true;
});
console.log(nextTodos.length); // 3
console.log(todos[0].done); // false
console.log(nextTodos[0].done); // true
nextTodos[1].done = true; // ypeError: Cannot assign to read only property 'done' of object '#<Object>'
源码复刻
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 JSON.parse(JSON.stringify(proxy))
}
03 - 状态简化(使用 useShallow 避免不必要渲染)
// import { create } from "zustand";
// interface BearState {
// bears: number;
// }
// interface BearActions {
// increasePopulation: () => void;
// removeAllBears: () => void;
// updateBears: (newBears: number) => void;
// getBears: () => number; // getter 类型
// }
// export type BearStore = BearState & BearActions;
// const useBear = create<BearStore>((set, get) => ({
// bears: 0,
// increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
// removeAllBears: () => set({ bears: 0 }),
// updateBears: (newBears: number) => set({ bears: newBears }),
// // getter 写成函数,每次调用取最新值
// getBears: () => get().bears,
// }));
// export default useBear;
import useBear from '@/stores/useBear';
import { useShallow } from 'zustand/react/shallow';
// ❌ 方法1:直接解构
// 不同组件之间,只要有一个属性发生改变,全部重新渲染
const { increasePopulation } = useBear();
// ❌ 方法2:使用选择器
// 增加心智负担
const updateBears = useBear((state) => state.updateBears);
// ✅ 方法3:useShallow
const { removeAllBears, bears } = useUserStore(useShallow((state) => ({
removeAllBears: state.removeAllBears,
bears: state.bears
})))
04 - middleware中间件
自定义中间件
/**
* 创建 store 的配置函数(原始函数,由用户传入,中间件会包装它)
* @param {function} set - 原始状态更新函数,用于修改 store 的状态
* @param {function} get - 获取当前 store 的状态值
* @param {object} api - store 的完整 API,包括 setState、getState、subscribe、destroy 和 actions
* @returns {StoreApi} 返回一个完整的 store 对象
*/
const logger = (config) => (set, get, api) => config((...args) => {
console.log(api)
console.log('before', get())
set(...args)
console.log('after', get())
}, get, api)
devtools/persist/combine
- devtool -> 引入浏览器插件Redux DevTools,实现可视化监控
- persist -> 将数据存到localStorage,useStore.persist.clearStorage 清除持久化
- combine -> 将state和actions分离
AI Chat 项目实例
import { create } from "zustand";
import { persist, combine, createJSONStorage, devtools } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
interface ChatStoreState {
currentModel: string;
hydrated: boolean;
}
interface ChatStoreActions {
setCurrentModel: (model: string) => void;
setHydrated: (val: boolean) => void;
}
type ChatStore = ChatStoreState & ChatStoreActions;
// 创建 store
const useChatStore = create<ChatStore>()(
devtools(
persist(
immer(
combine<ChatStoreState, ChatStoreActions>(
// state
{
currentModel: "deepseek-ai/DeepSeek-V3.2",
hydrated: false,
},
// actions
(set) => ({
setCurrentModel: (model: string) =>
set((state) => {
state.currentModel = model;
}),
setHydrated: (val: boolean) =>
set((state) => {
state.hydrated = val;
}),
})
)
),
{
// localstorage 的 key
name: "chat",
storage: createJSONStorage(() => localStorage),
// 避免组件在 store 数据还没恢复时渲染空数据或错误 UI
onRehydrateStorage: () => (state) => {
state?.setHydrated(true);
},
// 部分存入
partialize: (state) => ({
currentModel: state.currentModel,
}),
}
),
{
name: "ChatStore-DevTools", // 默认名称
enabled: true, // 是否打开
}
)
);
export type { ChatStore };
export default useChatStore;
05 - subscribe订阅
订阅允许在状态变化时执行副作用逻辑,而不会自动触发组件重渲染。
基本使用
const store = create((set) => ({
count: 0,
}))
// 全局订阅(没选selector就是订阅整个state)
store.subscribe((state) => {
console.log(state.count)
})
// 组件内部订阅(没选selector就是订阅整个state)
useEffect(() => {
store.subscribe((state) => {
console.log(state.count)
})
}, [])
取消订阅
import { subscribeWithSelector } from "zustand/middleware"
const store = create(
subscribeWithSelector((set) => ({
age: 0,
name: "张三",
}))
)
const [status, setStatus] = useState("小孩")
useEffect(() => {
// 只会更新一次组件
const unSub = useStore.subscribe(
// 只监控 age
(state) => state.age,
(age, prevAge) => {
if (age >= 18) {
setStatus("小孩")
unSub()
} else {
setStatus("大人")
}
},
{
equalityFn: (a, b) => a === b, // 默认是浅比较(只比较引用)
fireImmediately: true, // 立即触发,默认false
}
)
return () => unSub() // 组件卸载时清理
}, [])
附录 - Zustand中间件顺序
import { create } from "zustand";
import {
devtools,
persist,
subscribeWithSelector,
combine,
immer,
} from "zustand/middleware";
interface AppState {
count: number;
}
interface AppActions {
increment: () => void;
}
type AppStore = AppState & AppActions;
const useStore = create<AppStore>()(
devtools(
persist(
immer(
subscribeWithSelector(
combine<AppState, AppActions>(
{ count: 0 },
(set) => ({
increment: () => {
set((state) => {
state.count += 1;
});
},
})
)
)
),
{ name: "app" }
),
{ name: "AppStore" }
)
);
export default useStore;