状态管理规范
核心原则
必须使用 Zustand 作为全局状态管理方案,遵循单一职责原则拆分 Store,应该使用选择器优化性能。
Store 创建
基础 Store
typescript
// src/stores/counter.ts
import { create } from 'zustand';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export 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 }),
}));在组件中使用
typescript
// src/components/Counter.tsx
import { useCounterStore } from '@/stores/counter';
import { Button } from '@/components/ui/button';
export function Counter() {
const { count, increment, decrement, reset } = useCounterStore();
return (
<div className="flex items-center gap-4">
<Button onClick={decrement}>-</Button>
<span className="text-2xl font-bold">{count}</span>
<Button onClick={increment}>+</Button>
<Button variant="outline" onClick={reset}>重置</Button>
</div>
);
}异步操作
异步 Action
typescript
// src/stores/user.ts
import { create } from 'zustand';
import { userService } from '@/services/user';
interface User {
id: number;
name: string;
email: string;
}
interface UserState {
user: User | null;
loading: boolean;
error: string | null;
fetchUser: (id: number) => Promise<void>;
updateUser: (data: Partial<User>) => Promise<void>;
logout: () => void;
}
export const useUserStore = create<UserState>((set, get) => ({
user: null,
loading: false,
error: null,
fetchUser: async (id) => {
set({ loading: true, error: null });
try {
const user = await userService.getById(id);
set({ user, loading: false });
} catch (error) {
set({ error: '获取用户信息失败', loading: false });
}
},
updateUser: async (data) => {
const currentUser = get().user;
if (!currentUser) return;
set({ loading: true, error: null });
try {
const updatedUser = await userService.update(currentUser.id, data);
set({ user: updatedUser, loading: false });
} catch (error) {
set({ error: '更新用户信息失败', loading: false });
}
},
logout: () => {
set({ user: null, error: null });
},
}));在组件中使用异步操作
typescript
// src/pages/Profile.tsx
import { useEffect } from 'react';
import { useUserStore } from '@/stores/user';
import { LoadingSpinner } from '@/components/ui/loading-spinner';
export default function ProfilePage() {
const { user, loading, error, fetchUser } = useUserStore();
useEffect(() => {
fetchUser(1);
}, [fetchUser]);
if (loading) return <LoadingSpinner />;
if (error) return <div className="text-destructive">{error}</div>;
if (!user) return <div>用户不存在</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}选择器优化
使用选择器避免不必要的重渲染
typescript
// ❌ 错误 - 订阅整个 store,任何状态变化都会触发重渲染
function BadComponent() {
const store = useUserStore();
return <div>{store.user?.name}</div>;
}
// ✅ 正确 - 只订阅需要的状态
function GoodComponent() {
const userName = useUserStore((state) => state.user?.name);
return <div>{userName}</div>;
}选择多个状态
typescript
// 使用 shallow 比较优化
import { useShallow } from 'zustand/shallow';
function UserProfile() {
const { name, email } = useUserStore(
useShallow((state) => ({
name: state.user?.name,
email: state.user?.email,
}))
);
return (
<div>
<p>{name}</p>
<p>{email}</p>
</div>
);
}派生状态选择器
typescript
// src/stores/cart.ts
import { create } from 'zustand';
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
interface CartState {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: number) => void;
clearCart: () => void;
}
export const useCartStore = create<CartState>((set) => ({
items: [],
addItem: (item) =>
set((state) => ({
items: [...state.items, item],
})),
removeItem: (id) =>
set((state) => ({
items: state.items.filter((item) => item.id !== id),
})),
clearCart: () => set({ items: [] }),
}));
// 派生状态选择器(在组件外定义)
export const selectCartTotal = (state: CartState) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
export const selectCartItemCount = (state: CartState) =>
state.items.reduce((sum, item) => sum + item.quantity, 0);typescript
// 在组件中使用派生选择器
function CartSummary() {
const total = useCartStore(selectCartTotal);
const itemCount = useCartStore(selectCartItemCount);
return (
<div>
<p>商品数量: {itemCount}</p>
<p>总价: ¥{total}</p>
</div>
);
}状态分片
按领域拆分 Store
src/stores/
├── auth.ts # 认证状态
├── user.ts # 用户信息
├── cart.ts # 购物车
├── ui.ts # UI 状态(模态框、侧边栏等)
└── index.ts # 统一导出UI 状态 Store
typescript
// src/stores/ui.ts
import { create } from 'zustand';
interface UIState {
sidebarOpen: boolean;
theme: 'light' | 'dark' | 'system';
toggleSidebar: () => void;
setTheme: (theme: 'light' | 'dark' | 'system') => void;
}
export const useUIStore = create<UIState>((set) => ({
sidebarOpen: true,
theme: 'system',
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
setTheme: (theme) => set({ theme }),
}));统一导出
typescript
// src/stores/index.ts
export { useAuthStore } from './auth';
export { useUserStore } from './user';
export { useCartStore, selectCartTotal, selectCartItemCount } from './cart';
export { useUIStore } from './ui';持久化
使用 persist 中间件
typescript
// src/stores/auth.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface AuthState {
token: string | null;
isAuthenticated: boolean;
setToken: (token: string) => void;
clearToken: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
isAuthenticated: false,
setToken: (token) => set({ token, isAuthenticated: true }),
clearToken: () => set({ token: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => localStorage),
// 只持久化部分状态
partialize: (state) => ({ token: state.token }),
}
)
);会话存储
typescript
// src/stores/session.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
export const useSessionStore = create()(
persist(
(set) => ({
// ...状态
}),
{
name: 'session-storage',
storage: createJSONStorage(() => sessionStorage),
}
)
);中间件
日志中间件(开发环境)
typescript
// src/stores/user.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
export const useUserStore = create<UserState>()(
devtools(
(set) => ({
// ...状态和方法
}),
{ name: 'UserStore' }
)
);组合多个中间件
typescript
// src/stores/auth.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
export const useAuthStore = create<AuthState>()(
devtools(
persist(
(set) => ({
// ...状态和方法
}),
{ name: 'auth-storage' }
),
{ name: 'AuthStore' }
)
);最佳实践
Store 命名约定
- Store Hook 以
use开头,以Store结尾:useUserStore - 选择器函数以
select开头:selectCartTotal - 文件名与领域对应:
user.ts、cart.ts
状态设计原则
typescript
// ✅ 正确 - 只存储必要的状态
interface UserState {
user: User | null;
loading: boolean;
error: string | null;
}
// ❌ 错误 - 存储可派生的状态
interface BadUserState {
user: User | null;
loading: boolean;
error: string | null;
isLoggedIn: boolean; // 可从 user 派生
userName: string; // 可从 user 派生
}禁止的做法
- ❌ 禁止使用 Redux、MobX 等其他状态管理库
- ❌ 禁止在 Store 中存储可派生的状态
- ❌ 禁止订阅整个 Store(应使用选择器)
- ❌ 禁止在 Store 中存储大量数据(应使用服务端状态管理如 React Query)
- ❌ 禁止在 Store 中存储表单状态(应使用 React Hook Form)
- ❌ 禁止省略 TypeScript 类型定义