核心
Zustand 是一个轻量级、灵活强大的 React 状态管理库,采用 hooks API 设计。它提供简洁的 API 和出色的性能,是中小型项目的理想选择。
安装
npm install zustand
# yarn add zustand
# pnpm add zustand
一、创建 Store
使用 create 函数定义全局状态容器。
- JavaScript
- TypeScript
store.js
import create from 'zustand';
// 基础用法
const useStore = create(set => ({
count: 0,
increment: () => set({ count: state => state.count + 1 }),
decrement: () => set({ count: state => state.count - 1 }),
reset: () => set({ count: 0 }),
}));
// 带中间件的用法(见下文)
export default useStore;
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
const useCounterStore = create<COunterState>(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
set函数:更新状态,支持直接赋值或函数式更新- 状态可以是任意类型(数字、字符串、对象等)
- 修改方法通过
set更新状态,支持函数式更新(依赖当前状态)和直接赋值 - 返回值:返回包含状态和操作的对象
二、组件中订阅状态
1. 选择器(Selector) - 推荐方式
在组件中选择性订阅状态片段,避免不必要的重渲染。
import useStore from './store';
function Counter() {
// 只订阅 count 状态
const count = userStore(state => state.count);
// 订阅多个状态
const { increment, decrement } = useStore(state => ({
increment: state.increment,
decrement: state.decrement,
}));
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}
- 选择器函数:
(state) => value指定订阅的数据 - 自动优化:仅当选择的数据变化时组件重渲染
- 性能对比:
- 订阅整个状态时,任何状态变更都会触发组件重渲染
- 使用选择器时,仅当订阅的字段变更时触发重渲染
2. 使用 shallow 比较
避免因对象引用变化导致的重新渲染:
import { shallow } from 'zustand/shallow';
// 仅当 items 或 loading 变化时重新渲染
const { items, loading } = useCounterStore(
state => ({
items: state.items,
loading: state.loading,
}),
shallow,
);
3. 订阅单个状态(使用 subscribeWithSelector)
避免订阅整个 store 的变化,只关注特定状态:
import { create, subScribeWithSelector } from 'zustand';
// 创建带订阅功能的 Store
const useAgeStore = create(
subScribeWithSelector(set => ({
age: 0,
name: '张三',
})),
);
// 仅订阅 age 状态变化
const [status, setStatus] = useState('单身');
useAgeStore.subscribe(
state => state.age,
(age, preAge) => {
if (age >= 26) {
setStatus('结婚');
} else {
setStatus('单身');
}
},
);
三、 使用中间件
中间件增强 store 功能(日志、持久化等)
1. devtools (调试工具)
需要安装浏览器插件 Redux Devtools。
import { devtools } from 'zustand/middleware';
const useUserStore = create(
devtools(
set => ({
name: 'Tom',
age: 86,
hobby: {
sing: 'All in How Much We Give',
dance: 'Jazz dance',
},
setHobbyRap: rap =>
set(state => {
state.bobby.sing = rap;
}),
}),
{
enabled: true,
name: '用户信息',
},
),
);
2. persist (持久化储存)
将状态保存到 localStorage、sessionStorage 等。
import create from 'zustand';
import { devtools, persist } from 'zustand/middleware';
const useStore = create(
persist(
set => ({
name: 'Tom',
age: 86,
hobby: {
sing: 'All in How Much We Give',
dance: 'Jazz dance',
},
serUser: user => set({ user }),
}),
{
name: 'user-storage', // localStorage 键名
getStorage: createJsonStorage(() => sessionStorage), // 改用 sessionStorage
partialize: state => ({
name: state.name,
age: state.age,
hobby: state.bobby,
}),
},
),
);
persist: 状态持久化localStorage/sessionStorage
3. immer (简化不可变更新)
import { immer } from 'zustand/middleware';
const useUserStore = create(
immer(set => ({
name: 'Tom',
age: 86,
hobby: {
sing: 'All in How Much We Give',
dance: 'Jazz dance',
},
setHobbyRap: sing =>
set(state => {
state.hobby.sing = sing;
}),
})),
);
4. 自定义中间件
const loggerMiddleware = config => (set, get, api) => {
return config(
args => {
console.log('Dispatching', args);
set(args);
},
get,
api,
);
};
const useStore = create(
loggerMiddleware(set => ({
todos: [],
addTodo: text =>
set(state => ({
todos: [...state.todos, { text, id: Date.now() }],
})),
})),
);
5. 组合中间件
import { combine } from 'zustand/middleware';
const useStore = create(
combine({ count: 0 }, set => ({
increment: () =>
set(state => ({
count: state.count + 1,
})),
})),
devtools(),
persist(),
);
四、 高级用法
1. 异步操作
const useStore = create(set => ({
data: null,
loading: false,
fetchData: async () => {
set({ loading: true });
try {
const res = await fetch('/api/data');
const data = await res.json();
set({ data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
}));
2. 派生状态
const useStore = create(set => ({
items: [],
addItem: item =>
set(state => ({
items: [...state.item, item],
})),
// 派生状态不需要在 store 中定义
get itemCount() {
return this.items.length;
},
}));
// 在组件中使用
const count = useStore(state => state.itemCount);
3. 拆分 Store
避免单个大 Store 导致的性能问题:
// 用户状态
const useUserStore = create(/** */);
// 购物车状态
const useCartStore = create(/** */);
五、使用总结
| 特性 | 说明 |
|---|---|
无 Provider | 不需要包裹组件树 |
| 精准订阅 | 组件只订阅选择的数据片段 |
| 轻量级 | ~3kb gipped |
| 中间件生态 | 支持日志、持久化、中间件组合等 |
| TypeScript 友好 | 自动推断类型,支持泛型定义 |