Zustand(德语意为"状态")是一个小巧、快速、可扩展的 React 状态管理解决方案。
核心哲学
- 极简 API:只需一个 hook 调用
- 无样板代码:无需 Provider 包裹
- 不可变更新:类似 Redux 但更简单
- 类型安全:一流的 TypeScript 支持
安装
npm install zustand
# 或
pnpm add zustand
基础用法
1. 创建 Store
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
// 基础 store
const useBearStore = create((set, get) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
reset: () => set({ bears: 0 }),
// 异步 action
fetchBears: async () => {
const response = await fetch('/api/bears');
const { data } = await response.json();
set({ bears: data });
},
// 在 action 中访问当前状态
increaseBy: (amount) => {
const currentBears = get().bears;
set({ bears: currentBears + amount });
},
}));
// 带 TypeScript 的类型定义
interface BearStore {
bears: number;
fish: number;
increasePopulation: () => void;
eatFish: () => void;
addBears: (amount: number) => void;
}
const useBearStore = create<BearStore>((set, get) => ({
bears: 0,
fish: 10,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
eatFish: () => set((state) => ({ fish: state.fish - 1 })),
addBears: (amount) => set((state) => ({ bears: state.bears + amount })),
}));
2. 在组件中使用
function BearCounter() {
// 获取整个 store
const { bears, increasePopulation, removeAllBears } = useBearStore();
return (
<div>
<h1>{bears} bears around here</h1>
<button onClick={increasePopulation}>Add bear</button>
<button onClick={removeAllBears}>Remove all bears</button>
</div>
);
}
// 性能优化:选择特定状态
function BearCounterOptimized() {
// 只订阅 bears 的变化
const bears = useBearStore((state) => state.bears);
const increasePopulation = useBearStore((state) => state.increasePopulation);
return (
<div>
<h1>{bears} bears</h1>
<button onClick={increasePopulation}>Add bear</button>
</div>
);
}
// 批量选择多个状态
function BearCounterMulti() {
const { bears, fish, increasePopulation, eatFish } = useBearStore(
(state) => ({
bears: state.bears,
fish: state.fish,
increasePopulation: state.increasePopulation,
eatFish: state.eatFish,
}),
// 自定义相等性检查(可选)
(oldState, newState) =>
oldState.bears === newState.bears &&
oldState.fish === newState.fish
);
return (
<div>
<p>Bears: {bears}</p>
<p>Fish: {fish}</p>
<button onClick={increasePopulation}>Add Bear</button>
<button onClick={eatFish}>Eat Fish</button>
</div>
);
}
高级特性
1. 中间件系统
import { create } from 'zustand';
import {
devtools, // Redux DevTools 集成
persist, // 持久化
subscribeWithSelector, // 选择器订阅
combine, // 组合多个 store
redux, // Redux 模式
} from 'zustand/middleware';
// DevTools 集成
const useStore = create(
devtools(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{ name: 'MyStore', enabled: process.env.NODE_ENV !== 'production' }
)
);
// 持久化存储(localStorage/sessionStorage)
const usePersistedStore = create(
persist(
(set, get) => ({
user: null,
token: '',
setUser: (userData) => set({ user: userData }),
clearUser: () => set({ user: null, token: '' }),
}),
// 第二个参数:持久化配置(写法一)
{
name: 'user-storage', // 存储键名
// 部分持久化
partialize: (state) => ({
user: state.user,
// token 不持久化
}),
}
/**
第二个参数:持久化配置(写法二)
{
name: 'counter-storage', // 存储的 key(必选,用于 localStorage 标识)
// 默认存储到 localStorage,可指定为 sessionStorage:
// storage: createJSONStorage(() => sessionStorage)
}
*/
)
);
// 订阅特定状态变化
const useStore = create(
subscribeWithSelector((set, get) => ({
count: 0,
text: 'hello',
increment: () => set((state) => ({ count: state.count + 1 })),
}))
);
// 在组件外订阅
const unsubscribe = useStore.subscribe(
(state) => state.count,
(count, prevCount) => {
console.log(`Count changed from ${prevCount} to ${count}`);
},
{
equalityFn: (a, b) => a === b, // 默认
fireImmediately: true, // 立即触发
}
);
2. Immer 集成(可变更新)
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
const useStore = create(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
// 可以直接修改状态,Immer 会处理不可变更新
state.todos.push({
id: Date.now(),
text,
completed: false
});
}),
toggleTodo: (id) =>
set((state) => {
const todo = state.todos.find((t) => t.id === id);
if (todo) todo.completed = !todo.completed;
}),
}))
);
3. Slice 模式(模块化)
// slices/counterSlice.ts
export interface CounterSlice {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
const createCounterSlice = (set, get): CounterSlice => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
});
// slices/userSlice.ts
export interface UserSlice {
user: User | null;
token: string;
login: (credentials: LoginCredentials) => Promise<void>;
logout: () => void;
}
const createUserSlice = (set, get): UserSlice => ({
user: null,
token: '',
login: async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
const data = await response.json();
set({ user: data.user, token: data.token });
},
logout: () => set({ user: null, token: '' }),
});
// 组合 slices
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
const useStore = create(
devtools((set, get) => ({
...createCounterSlice(set, get),
...createUserSlice(set, get),
}))
);
4. 计算属性/派生状态
const useStore = create((set, get) => ({
items: [],
filter: 'all',
// 直接在 store 中定义计算属性
get filteredItems() {
const { items, filter } = get();
switch (filter) {
case 'completed':
return items.filter(item => item.completed);
case 'active':
return items.filter(item => !item.completed);
default:
return items;
}
},
// 或者使用 getter 函数
getCompletedItems: () => {
const items = get().items;
return items.filter(item => item.completed);
},
// 基于现有状态的 action
completeAll: () => {
const items = get().items;
set({ items: items.map(item => ({ ...item, completed: true })) });
},
}));
实用模式和最佳实践
1. 类型安全的 Store 工厂
// store/types.ts
export interface StoreState {
// 状态
bears: number;
fish: number;
user: User | null;
// Actions
incrementBear: () => void;
decrementBear: () => void;
setUser: (user: User | null) => void;
fetchUser: (userId: string) => Promise<void>;
}
// store/createStore.ts
import { StateCreator } from 'zustand';
export const createBearSlice: StateCreator<
StoreState,
[['zustand/devtools', never]],
[],
Pick<StoreState, 'bears' | 'fish' | 'incrementBear' | 'decrementBear'>
> = (set) => ({
bears: 0,
fish: 10,
incrementBear: () => set((state) => ({ bears: state.bears + 1 })),
decrementBear: () => set((state) => ({ bears: state.bears - 1 })),
});
export const createUserSlice: StateCreator<
StoreState,
[['zustand/devtools', never]],
[],
Pick<StoreState, 'user' | 'setUser' | 'fetchUser'>
> = (set, get) => ({
user: null,
setUser: (user) => set({ user }),
fetchUser: async (userId) => {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
set({ user });
},
});
// store/index.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
export const useStore = create<StoreState>()(
devtools(
(...a) => ({
...createBearSlice(...a),
...createUserSlice(...a),
}),
{ name: 'AppStore' }
)
);
2. 异步操作模式
const useAsyncStore = create((set, get) => ({
data: null,
loading: false,
error: null,
fetchData: async (id) => {
// 开始加载
set({ loading: true, error: null });
try {
const response = await fetch(`/api/data/${id}`);
if (!response.ok) throw new Error('Network error');
const data = await response.json();
set({ data, loading: false });
} catch (error) {
set({ error: error.message, loading: false });
}
},
// 带取消的异步操作
fetchDataWithCancel: async (id) => {
const controller = new AbortController();
set({ loading: true, error: null });
try {
const response = await fetch(`/api/data/${id}`, {
signal: controller.signal,
});
const data = await response.json();
set({ data, loading: false });
} catch (error) {
if (error.name !== 'AbortError') {
set({ error: error.message, loading: false });
}
}
// 返回取消函数
return () => controller.abort();
},
}));
3. 表单处理
const useFormStore = create((set) => ({
formData: {
username: '',
email: '',
password: '',
},
errors: {},
touched: {},
// 更新单个字段
setField: (field, value) =>
set((state) => ({
formData: { ...state.formData, [field]: value },
// 标记为已触摸
touched: { ...state.touched, [field]: true },
})),
// 批量更新
setFormData: (updates) =>
set((state) => ({
formData: { ...state.formData, ...updates },
})),
// 验证
validate: () => {
const { formData } = get();
const errors = {};
if (!formData.email.includes('@')) {
errors.email = 'Invalid email';
}
set({ errors });
return Object.keys(errors).length === 0;
},
// 提交
submit: async () => {
if (!validate()) return false;
try {
const response = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(get().formData),
});
return response.ok;
} catch (error) {
set({ errors: { submit: error.message } });
return false;
}
},
// 重置
reset: () => set({
formData: { username: '', email: '', password: '' },
errors: {},
touched: {},
}),
}));
4. 性能优化
// 1. 使用选择器避免不必要的重渲染
function ExpensiveComponent() {
// ❌ 错误:订阅整个 store
const store = useStore();
// ✅ 正确:只订阅需要的部分
const value = useStore((state) => state.expensiveValue);
// ✅ 使用 shallow 比较
const { prop1, prop2 } = useStore(
(state) => ({
prop1: state.prop1,
prop2: state.prop2
}),
shallow // 来自 zustand/shallow
);
}
// 2. 防抖更新
const useDebouncedStore = create((set) => ({
searchQuery: '',
_setSearchQuery: (query) => set({ searchQuery: query }),
}));
// 在组件中使用防抖
function SearchInput() {
const setSearchQuery = useDebouncedStore((state) => state._setSearchQuery);
const [localQuery, setLocalQuery] = useState('');
useEffect(() => {
const timeoutId = setTimeout(() => {
setSearchQuery(localQuery);
}, 300);
return () => clearTimeout(timeoutId);
}, [localQuery, setSearchQuery]);
return <input value={localQuery} onChange={(e) => setLocalQuery(e.target.value)} />;
}
// 3. 批量更新
const useBatchStore = create((set) => ({
a: 0,
b: 0,
c: 0,
// 批量更新多个状态
updateAll: (values) => set((state) => ({
a: values.a ?? state.a,
b: values.b ?? state.b,
c: values.c ?? state.c,
})),
}));
5. 测试策略
// store/__tests__/bearStore.test.js
import { act } from 'react-test-renderer';
import { create } from 'zustand';
// 测试 store
const useTestStore = create((set) => ({
bears: 0,
increment: () => set((state) => ({ bears: state.bears + 1 })),
}));
describe('Bear Store', () => {
it('should increment bears', () => {
const { getState, setState } = useTestStore;
expect(getState().bears).toBe(0);
act(() => {
getState().increment();
});
expect(getState().bears).toBe(1);
});
it('should handle initial state', () => {
const useCustomStore = create(() => ({
bears: 10,
}));
expect(useCustomStore.getState().bears).toBe(10);
});
});
// 使用测试工具
import { create as actualCreate } from 'zustand';
import { subscribeWithSelector } from 'zustand/middleware';
// Mock store 用于测试
const createMockStore = (initialState) => {
let store = null;
const api = {
getState: jest.fn(),
setState: jest.fn(),
subscribe: jest.fn(),
};
store = actualCreate(
subscribeWithSelector(() => ({
...initialState,
}))
);
api.getState = store.getState;
api.setState = store.setState;
api.subscribe = store.subscribe;
return api;
};
生态系统
1. 官方中间件
# Immer 集成
npm install immer
# 持久化
npm install zustand/middleware
# 或者使用社区方案
npm install zustand-persist
npm install zustand-xstate # XState 集成
2. 常用搭配
// 1. 与 React Query 配合
const useStore = create((set) => ({
// 本地状态
filters: {},
sortBy: 'date',
// 服务器状态由 React Query 管理
}));
// 2. 与 Formik/Yup 配合
const useFormStore = create((set) => ({
// ... 表单状态
}));
// 3. 与 React Router 集成
const useRouterStore = create((set) => ({
pathname: window.location.pathname,
navigate: (path) => {
window.history.pushState({}, '', path);
set({ pathname: path });
},
}));
优缺点对比
优点:
- 极简 API:学习成本极低
- 无 Provider 地狱:不需要包裹组件
- 优秀的 TypeScript 支持
- 性能卓越:细粒度更新
- 中间件生态丰富
- 包体积极小(~1KB)
缺点:
- 过于灵活:缺乏强制性的最佳实践
- 调试相对困难(需依赖 DevTools)
- 缺少 Redux 的中间件生态系统
适用场景
- 中小型 React 应用
- 需要快速上手的项目
- 不喜欢 Redux 复杂度的团队
- 需要轻量级状态管理的场景
- 与 React Query 配合使用
Zustand 是目前 React 社区最受欢迎的状态管理库之一,特别适合那些想要简单、直观且功能强大的状态管理方案的团队。