Zustand 状态管理工具用法详解
Zustand 是一款轻量级、无 Provider 包裹、API 简洁的状态管理工具,核心优势是上手快、代码量少、不依赖 React 上下文,适合中小型项目或大型项目中的独立模块状态管理。
一、核心特点
- 无 Provider 嵌套:不需要像 Redux/Vuex 那样用 Provider 包裹整个应用,直接创建 Store 即可使用;
- 简洁 API :通过
create函数创建 Store,useStore钩子订阅状态,学习成本极低; - 支持中间件:内置持久化、日志、撤销/重做等中间件,扩展能力强;
- 轻量化:体积仅 1KB 左右,无多余依赖;
- React 友好:天然支持 React hooks,也可用于 Vue/原生 JS 项目。
二、安装
bash
# npm
npm install zustand
# yarn
yarn add zustand
# pnpm
pnpm add zustand
三、基础用法(React 场景)
1. 创建 Store(核心步骤)
Store 是状态的容器,包含「状态数据」和「修改状态的方法」,通常单独创建文件(如 src/store/index.js):
javascript
import { create } from 'zustand';
// 创建 Store:参数是一个函数,返回 { 状态, 状态修改方法 }
const useCounterStore = create((set) => ({
// 1. 状态数据
count: 0,
name: 'Zustand',
// 2. 修改状态的方法(通过 set 函数更新状态)
// set 接收:(prevState) => newState,支持函数式更新(确保拿到最新状态)
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
// 带参数的修改方法
setCount: (newCount) => set({ count: newCount }), // 直接传对象(无需依赖旧状态时)
// 批量修改多个状态
reset: () => set({ count: 0, name: 'Zustand Reset' }),
}));
export default useCounterStore;
create函数:接收一个「状态初始化函数」,返回一个「自定义 hooks」(如useCounterStore);set函数:用于修改状态,支持两种写法:- 函数式:
set(prev => ({ count: prev.count + 1 }))(依赖旧状态时必须用); - 对象式:
set({ count: 10 })(不依赖旧状态时简化使用)。
- 函数式:
2. 组件中使用 Store
通过自定义 hooks useCounterStore 订阅状态和调用方法,支持「订阅整个 Store」或「订阅部分状态」(性能更优):
jsx
import useCounterStore from '@/store';
function Counter() {
// 方式1:订阅单个状态(推荐,减少不必要的重渲染)
const count = useCounterStore((state) => state.count);
const name = useCounterStore((state) => state.name);
// 方式2:订阅多个状态(通过 shallow 浅比较,避免因对象引用变化导致重渲染)
const { increment, decrement } = useCounterStore(
(state) => ({
increment: state.increment,
decrement: state.decrement,
}),
{ equals: shallow } // 需要导入:import { shallow } from 'zustand/shallow'
);
// 方式3:订阅整个 Store(不推荐,状态变化时组件会全量重渲染)
// const store = useCounterStore();
return (
<div>
<h1>{name}</h1>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={() => useCounterStore.getState().setCount(100)}>
直接修改(非组件环境可用)
</button>
</div>
);
}
- 关键 API:
useCounterStore(selector):订阅状态,selector函数返回需要的状态/方法;useCounterStore.getState():非组件环境(如工具函数、定时器)获取当前状态;useCounterStore.setState(newState):非组件环境修改状态(如useCounterStore.setState({ count: 10 }));shallow浅比较:当订阅多个状态时,用shallow避免因对象引用变化导致的不必要重渲染(默认是全等比较)。
3. 异步状态管理(常见场景)
Zustand 天然支持异步方法,直接在 Store 中定义异步函数即可(无需额外中间件):
javascript
import { create } from 'zustand';
import axios from 'axios';
// 示例:用户信息 Store
const useUserStore = create((set) => ({
userInfo: null,
loading: false,
error: null,
// 异步获取用户信息
fetchUser: async (userId) => {
set({ loading: true, error: null }); // 开始请求:更新加载状态
try {
const res = await axios.get(`/api/user/${userId}`);
set({ userInfo: res.data, loading: false }); // 请求成功:更新用户信息
} catch (err) {
set({ error: err.message, loading: false }); // 请求失败:更新错误信息
}
},
// 退出登录:重置状态
logout: () => set({ userInfo: null }),
}));
export default useUserStore;
组件中使用:
jsx
function UserProfile() {
const { userInfo, loading, error, fetchUser, logout } = useUserStore(
(state) => ({
userInfo: state.userInfo,
loading: state.loading,
error: state.error,
fetchUser: state.fetchUser,
logout: state.logout,
}),
{ equals: shallow }
);
useEffect(() => {
fetchUser(1); // 组件挂载时获取用户信息
}, [fetchUser]);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error}</div>;
if (!userInfo) return <div>未登录</div>;
return (
<div>
<h1>{userInfo.name}</h1>
<button onClick={logout}>退出登录</button>
</div>
);
}
四、进阶用法
1. 持久化存储(localStorage/sessionStorage)
通过 zustand/persist 中间件实现状态持久化,刷新页面后状态不丢失:
javascript
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useAuthStore = create(
persist(
(set) => ({
token: null,
login: (token) => set({ token }),
logout: () => set({ token: null }),
}),
{
name: 'auth-storage', // 存储在 localStorage 中的 key(默认是 'zustand')
getStorage: () => localStorage, // 可选:指定存储方式(localStorage/sessionStorage)
// 可选:自定义序列化/反序列化(如加密敏感数据)
// serialize: (state) => JSON.stringify({ ...state, token: encrypt(token) }),
// deserialize: (str) => { const state = JSON.parse(str); return { ...state, token: decrypt(token) }; }
}
)
);
export default useAuthStore;
- 原理:将状态自动同步到
localStorage,页面刷新时从localStorage恢复状态; - 注意:持久化的状态需是可序列化的(不能包含函数、Symbol 等)。
2. 拆分多个 Store(模块化)
大型项目中可按功能拆分多个 Store(如用户、购物车、设置),互不干扰:
javascript
// src/store/userStore.js
import { create } from 'zustand';
export const useUserStore = create((set) => ({
userInfo: null,
setUserInfo: (info) => set({ userInfo: info }),
}));
// src/store/cartStore.js
import { create } from 'zustand';
export const useCartStore = create((set) => ({
cartList: [],
addToCart: (goods) => set((state) => ({ cartList: [...state.cartList, goods] })),
}));
组件中按需导入:
jsx
import { useUserStore } from '@/store/userStore';
import { useCartStore } from '@/store/cartStore';
function App() {
const userInfo = useUserStore((state) => state.userInfo);
const addToCart = useCartStore((state) => state.addToCart);
// ...
}
3. 中间件:日志、撤销/重做
Zustand 支持自定义中间件,内置常用中间件:
(1)日志中间件(log)
打印状态变化日志(开发环境用):
javascript
import { create } from 'zustand';
import { log } from 'zustand/middleware';
const useCounterStore = create(
log((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
);
- 效果:每次状态变化时,控制台会打印「旧状态→新状态→触发的动作」。
(2)撤销/重做中间件(undo)
支持状态的撤销(undo)和重做(redo):
javascript
import { create } from 'zustand';
import { undo } from 'zustand/middleware';
const useEditStore = create(
undo((set) => ({
content: '',
setContent: (text) => set({ content: text }),
}))
);
// 组件中使用
function Editor() {
const { content, setContent, undo, redo, canUndo, canRedo } = useEditStore();
return (
<div>
<textarea value={content} onChange={(e) => setContent(e.target.value)} />
<button onClick={undo} disabled={!canUndo}>撤销</button>
<button onClick={redo} disabled={!canRedo}>重做</button>
</div>
);
}
4. 状态派生(计算属性)
通过 selector 函数实现派生状态(类似 Vuex 的 getters):
javascript
import { create } from 'zustand';
const useCartStore = create((set) => ({
cartList: [
{ id: 1, name: '手机', price: 5000, quantity: 2 },
{ id: 2, name: '耳机', price: 1000, quantity: 1 },
],
addToCart: (goods) => set((state) => ({ cartList: [...state.cartList, goods] })),
}));
// 组件中派生「购物车总价」
function Cart() {
// 派生状态:计算总价(依赖 cartList,cartList 变化时自动更新)
const totalPrice = useCartStore((state) =>
state.cartList.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
return <div>总价:{totalPrice} 元</div>;
}
五、与 Redux 的对比(为什么选 Zustand?)
| 特性 | Zustand | Redux |
|---|---|---|
| 代码量 | 极少(无 Provider、Action Type) | 较多(需定义 Action、Reducer、Store) |
| 上手难度 | 低(API 简洁) | 高(需理解 Redux 三大原则) |
| 状态订阅 | 精确订阅(按需更新) | 需配合 reselect 优化订阅 |
| 中间件 | 内置支持(持久化、日志等) | 需手动集成(redux-thunk、redux-saga) |
| 适用场景 | 中小型项目、独立模块 | 大型复杂项目(需严格状态管控) |
六、最佳实践
- 按需订阅状态 :尽量用
selector订阅单个/部分状态,避免订阅整个 Store(减少重渲染); - 多个状态订阅用
shallow:订阅多个状态时,添加{ equals: shallow }浅比较; - 拆分模块化 Store:按功能拆分 Store(如用户、购物车、设置),避免单个 Store 过大;
- 敏感数据加密 :持久化存储时(如 token),对敏感数据进行加密(自定义
serialize方法); - 非组件环境使用 :通过
useStore.getState()/useStore.setState()在工具函数、定时器中操作状态; - 避免状态中存函数:状态应是可序列化的数据(函数、React 组件等不建议存入,可能导致持久化失败)。
七、常见问题
1. 组件不重渲染?
-
原因1:订阅状态时返回新对象/数组(导致全等比较失败),需用
shallow浅比较; -
原因2:修改状态时未用
set函数(直接修改原状态不会触发更新); -
解决:
javascript// 错误:直接修改原状态(不触发更新) const store = useCounterStore.getState(); store.count = 10; // 正确:用 set 函数修改 useCounterStore.setState({ count: 10 });
2. 持久化状态不生效?
- 检查状态是否可序列化(不能包含函数、Symbol 等);
- 确认
getStorage配置正确(localStorage/sessionStorage); - 清除浏览器缓存后重试。