Skip to content

状态管理规范

核心原则

必须使用 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.tscart.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 类型定义