第 155 章:状态管理 Zustand
学习目标
- 理解 Zustand 的简洁 API
- 掌握 store 创建与使用
- 学会选择器(selector)优化
- 替代 useContext 的轻量方案
一、Zustand 是什么
Zustand(德语"状态")是轻量级全局状态管理库,无需 Provider,API 简单。
二、安装
bash
pnpm add zustand三、基础用法
3.1 创建 store
tsx
// stores/useUserStore.ts
import { create } from 'zustand';
interface User {
id: number;
name: string;
}
interface UserState {
user: User | null;
setUser: (user: User) => void;
clear: () => void;
}
export const useUserStore = create<UserState>((set) => ({
user: null,
setUser: (user) => set({ user }),
clear: () => set({ user: null }),
}));3.2 在组件使用
tsx
'use client';
import { useUserStore } from '@/stores/useUserStore';
function UserInfo() {
const user = useUserStore(s => s.user);
const setUser = useUserStore(s => s.setUser);
const clear = useUserStore(s => s.clear);
if (!user) return <button onClick={() => setUser({ id: 1, name: 'Tom' })}>
登录
</button>;
return (
<div>
<p>{user.name}</p>
<button onClick={clear}>登出</button>
</div>
);
}四、计数器示例
tsx
// stores/useCountStore.ts
import { create } from 'zustand';
interface CountState {
count: number;
inc: () => void;
dec: () => void;
reset: () => void;
}
export const useCountStore = create<CountState>((set) => ({
count: 0,
inc: () => set((s) => ({ count: s.count + 1 })),
dec: () => set((s) => ({ count: s.count - 1 })),
reset: () => set({ count: 0 }),
}));tsx
// 组件
function Counter() {
const count = useCountStore(s => s.count);
const inc = useCountStore(s => s.inc);
return <button onClick={inc}>{count}</button>;
}五、持久化
bash
pnpm add zustand/middlewaretsx
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AuthState {
token: string | null;
setToken: (token: string) => void;
clear: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
setToken: (token) => set({ token }),
clear: () => set({ token: null }),
}),
{ name: 'auth-storage' } // localStorage key
)
);六、切片模式(大型应用)
tsx
// stores/useAppStore.ts
import { create } from 'zustand';
import { createUserSlice, UserSlice } from './userSlice';
import { createCartSlice, CartSlice } from './cartSlice';
type AppState = UserSlice & CartSlice;
export const useAppStore = create<AppState>()((...a) => ({
...createUserSlice(...a),
...createCartSlice(...a),
}));tsx
// stores/userSlice.ts
import { StateCreator } from 'zustand';
export interface UserSlice {
user: User | null;
setUser: (user: User) => void;
}
export const createUserSlice: StateCreator<
UserSlice, [], [], UserSlice
> = (set) => ({
user: null,
setUser: (user) => set({ user }),
});七、性能优化:选择器
7.1 精确订阅
tsx
// ❌ 订阅整个 store,任何变化都重渲染
const state = useUserStore();
// ✅ 只订阅需要的字段
const user = useUserStore(s => s.user);7.2 shallow 比较
tsx
import { shallow } from 'zustand/shallow';
const { user, setUser } = useUserStore(
(s) => ({ user: s.user, setUser: s.setUser }),
shallow
);7.3 useShallow(v4+)
tsx
import { useShallow } from 'zustand/react/shallow';
const { user, setUser } = useUserStore(
useShallow((s) => ({ user: s.user, setUser: s.setUser }))
);八、组件外使用 store
tsx
// 不在 React 组件中访问 store
function logout() {
const { clear } = useUserStore.getState();
clear();
}
// 订阅变化
const unsub = useUserStore.subscribe((state) => {
console.log('user 变了', state.user);
});九、Zustand vs Redux vs Context
| 维度 | Zustand | Redux Toolkit | Context |
|---|---|---|---|
| 包大小 | 1KB | 10KB+ | 0(内置) |
| 模板代码 | 极少 | 中 | 少 |
| DevTools | 有 | 有 | 无 |
| 持久化 | middleware | 手写 | 手写 |
| 学习成本 | 低 | 中 | 低 |
| 适合 | 中小型 | 大型 | 简单共享 |
十、Next.js 注意事项
Zustand 在 Next.js 14 中需要小心 SSR:
tsx
// stores/useUserStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
export const useUserStore = create<...>()(
persist(
(set) => ({ ... }),
{
name: 'user',
storage: createJSONStorage(() => localStorage),
}
)
);tsx
// 在组件中避免 SSR 不一致
'use client';
import { useEffect, useState } from 'react';
import { useUserStore } from '@/stores/useUserStore';
function UserInfo() {
const [hydrated, setHydrated] = useState(false);
useEffect(() => setHydrated(true), []);
const user = useUserStore(s => s.user);
if (!hydrated) return null;
return <div>{user?.name}</div>;
}十一、本章小结
| 概念 | 关键 |
|---|---|
| create | 创建 store |
| 选择器 | 精确订阅,避免无关渲染 |
| persist | localStorage 持久化 |
| 切片 | 拆分大型 store |
| 中间件 | 持久化、immer 等 |
动手练习
- 写一个购物车 store(增删改查、计算总价)
- 加 persist 持久化
- 用选择器优化一个多字段组件
- 组件外修改 store(token 刷新后清空)
推荐阅读
下一章:第 156 章:TypeScript 集成 →