🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx @anthropic-ai/skills install supercent-io/skills-template/state-management

💡 提示:需要 Node.js 和 NPM

状态管理

何时使用此技能

  • 需要全局状态: 多个组件需要共享同一份数据
  • 遇到 Props 层层传递问题: 需要将 props 传递 5 层或更多层级的组件
  • 复杂的业务状态逻辑: 如用户认证、购物车、主题切换等
  • 状态同步需求: 需要将服务器数据与客户端状态进行同步

操作指南

步骤 1: 确定状态的作用范围

首先需要区分是局部状态还是全局状态。

判断标准

  • 局部状态:仅在单个组件内部使用
    • 例如:表单输入值、开关状态、下拉菜单的打开/关闭
    • 使用 useState, useReducer 即可
  • 全局状态:需要在多个不直接相连的组件间共享
    • 例如:用户认证信息、购物车内容、主题、语言设置
    • 需要使用 Context API, Redux, Zustand 等工具

示例

// ✅ 局部状态 (仅在当前组件使用)
function SearchBox() {
  const [query, setQuery] = useState('');
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onFocus={() => setIsOpen(true)}
      />
      {isOpen && <SearchResults query={query} />}
    </div>
  );
}

// ✅ 全局状态 (跨多个组件使用)
// 用户认证信息需要在 Header, Profile, Settings 等组件中使用
const { user, logout } = useAuth();  // 通过 Context 或 Zustand 实现

步骤 2: React Context API (适用于简单的全局状态)

适合轻量级的全局状态管理。

示例 (认证状态 Context):

// contexts/AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';

interface User {
  id: string;
  email: string;
  name: string;
}

interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = async (email: string, password: string) => {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });

    const data = await response.json();
    setUser(data.user);
    localStorage.setItem('token', data.token);
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem('token');
  };

  return (
    <AuthContext.Provider value={{
      user,
      login,
      logout,
      isAuthenticated: !!user
    }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义 Hook,方便使用 Context
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth 必须在 AuthProvider 内部使用');
  }
  return context;
}

使用方法

// App.tsx
function App() {
  return (
    <AuthProvider>
      <Router>
        <Header />
        <Routes />
      </Router>
    </AuthProvider>
  );
}

// Header.tsx
function Header() {
  const { user, logout, isAuthenticated } = useAuth();

  return (
    <header>
      {isAuthenticated ? (
        <>
          <span>欢迎, {user!.name}</span>
          <button onClick={logout}>退出登录</button>
        </>
      ) : (
        <Link to="/login">登录</Link>
      )}
    </header>
  );
}

步骤 3: Zustand (现代且简洁的状态管理库)

比 Redux 更简单,样板代码更少。

安装

npm install zustand

示例 (购物车):

// stores/cartStore.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

interface CartStore {
  items: CartItem[];
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: string) => void;
  updateQuantity: (id: string, quantity: number) => void;
  clearCart: () => void;
  total: () => number;
}

export const useCartStore = create<CartStore>()(
  devtools(
    persist(
      (set, get) => ({
        items: [],

        addItem: (item) => set((state) => {
          const existing = state.items.find(i => i.id === item.id);
          if (existing) {
            return {
              items: state.items.map(i =>
                i.id === item.id
                  ? { ...i, quantity: i.quantity + 1 }
                  : i
              )
            };
          }
          return { items: [...state.items, { ...item, quantity: 1 }] };
        }),

        removeItem: (id) => set((state) => ({
          items: state.items.filter(item => item.id !== id)
        })),

        updateQuantity: (id, quantity) => set((state) => ({
          items: state.items.map(item =>
            item.id === id ? { ...item, quantity } : item
          )
        })),

        clearCart: () => set({ items: [] }),

        total: () => {
          const { items } = get();
          return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
        }
      }),
      { name: 'cart-storage' }  // 存储在 localStorage 中的 key
    )
  )
);

使用方法

// components/ProductCard.tsx
function ProductCard({ product }) {
  const addItem = useCartStore(state => state.addItem);

  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={() => addItem(product)}>
        加入购物车
      </button>
    </div>
  );
}

// components/Cart.tsx
function Cart() {
  const items = useCartStore(state => state.items);
  const total = useCartStore(state => state.total());
  const removeItem = useCartStore(state => state.removeItem);

  return (
    <div>
      <h2>购物车</h2>
      {items.map(item => (
        <div key={item.id}>
          <span>{item.name} x {item.quantity}</span>
          <span>${item.price * item.quantity}</span>
          <button onClick={() => removeItem(item.id)}>移除</button>
        </div>
      ))}
      <p>总计: ${total.toFixed(2)}</p>
    </div>
  );
}

步骤 4: Redux Toolkit (适用于大型应用)

当你的应用状态逻辑非常复杂,并且需要使用中间件时,可以考虑 Redux Toolkit。

安装

npm install @reduxjs/toolkit react-redux

示例 (待办事项):

// store/todosSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';

interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

interface TodosState {
  items: Todo[];
  status: 'idle' | 'loading' | 'failed';
}

const initialState: TodosState = {
  items: [],
  status: 'idle'
};

// 异步 action (例如从 API 获取数据)
export const fetchTodos = createAsyncThunk('todos/fetch', async () => {
  const response = await fetch('/api/todos');
  return response.json();
});

const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    addTodo: (state, action: PayloadAction<string>) => {
      state.items.push({
        id: Date.now().toString(),
        text: action.payload,
        completed: false
      });
    },
    toggleTodo: (state, action: PayloadAction<string>) => {
      const todo = state.items.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    removeTodo: (state, action: PayloadAction<string>) => {
      state.items = state.items.filter(t => t.id !== action.payload);
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.status = 'idle';
        state.items = action.payload;
      })
      .addCase(fetchTodos.rejected, (state) => {
        state.status = 'failed';
      });
  }
});

export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
export default todosSlice.reducer;

// store/index.ts
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './todosSlice';

export const store = configureStore({
  reducer: {
    todos: todosReducer
  }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

使用方法

// App.tsx
import { Provider } from 'react-redux';
import { store } from './store';

function App() {
  return (
    <Provider store={store}>
      <TodoApp />
    </Provider>
  );
}

// components/TodoList.tsx
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from '../store';
import { toggleTodo, removeTodo } from '../store/todosSlice';

function TodoList() {
  const todos = useSelector((state: RootState) => state.todos.items);
  const dispatch = useDispatch();

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          <input
            type="checkbox"
            checked={todo.completed}
            onChange={() => dispatch(toggleTodo(todo.id))}
          />
          <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
            {todo.text}
          </span>
          <button onClick={() => dispatch(removeTodo(todo.id))}>删除</button>
        </li>
      ))}
    </ul>
  );
}

步骤 5: 服务端状态管理 (React Query / TanStack Query)

专门用于处理 API 数据获取、缓存和同步。

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

function UserProfile({ userId }: { userId: string }) {
  const queryClient = useQueryClient();

  // GET: 获取用户信息
  const { data: user, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: async () => {
      const res = await fetch(`/api/users/${userId}`);
      return res.json();
    },
    staleTime: 5 * 60 * 1000,  // 数据在 5 分钟内视为新鲜,不会重新请求
  });

  // POST/PUT/PATCH: 更新用户信息
  const mutation = useMutation({
    mutationFn: async (updatedUser: Partial<User>) => {
      const res = await fetch(`/api/users/${userId}`, {
        method: 'PATCH',
        body: JSON.stringify(updatedUser)
      });
      return res.json();
    },
    onSuccess: () => {
      // 更新成功后,使相关的查询失效,触发重新获取数据
      queryClient.invalidateQueries({ queryKey: ['user', userId] });
    }
  });

  if (isLoading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
      <button onClick={() => mutation.mutate({ name: '新用户名' })}>
        更新用户名
      </button>
    </div>
  );
}

输出格式

状态管理工具选型指南

根据场景推荐的工具:

1. 简单的全局状态 (如主题、语言)
   → React Context API

2. 中等复杂度 (如购物车、用户设置)
   → Zustand

3. 大型应用、复杂逻辑、需要中间件
   → Redux Toolkit

4. 服务端数据获取/缓存
   → React Query (TanStack Query)

5. 表单状态
   → React Hook Form + Zod

约束条件

强制性规则 (必须遵守)

  1. 状态不可变性:绝不能直接修改状态
    // ❌ 错误示例
    state.items.push(newItem);
    
    // ✅ 正确示例
    setState({ items: [...state.items, newItem] });
    
  2. 最小状态原则:不要存储可以通过现有状态计算得出的值
    // ❌ 错误示例
    const [items, setItems] = useState([]);
    const [count, setCount] = useState(0);  // 这个值完全可以通过 items.length 计算出来
    
    // ✅ 正确示例
    const [items, setItems] = useState([]);
    const count = items.length;  // 这是一个派生值
    
  3. 单一数据源:不要在多处重复存储同一份数据

禁止事项 (绝不能做)

  1. 过度的 Props 层层传递:当 props 需要传递超过 5 层时,禁止继续使用 props drilling
    – 应该使用 Context 或状态管理库
  2. 避免将所有状态都设为全局:能用局部状态解决问题时,优先使用局部状态

最佳实践

  1. 选择性订阅:只订阅组件需要用到的状态,避免不必要的重新渲染
    // ✅ 好:只订阅需要的 items
    const items = useCartStore(state => state.items);
    
    // ❌ 差:订阅了整个 store,任何变化都会导致组件重绘
    const { items, addItem, removeItem, updateQuantity, clearCart } = useCartStore();
    
  2. 清晰的 Action 命名:例如用 updateUserProfile 而不是模糊的 update
  3. 使用 TypeScript:确保类型安全,减少运行时错误

参考链接

元数据

版本信息

  • 当前版本:1.0.0
  • 最后更新:2025-01-01
  • 兼容平台:Claude, ChatGPT, Gemini

相关技能

标签

#状态管理 #React #Redux #Zustand #Context #全局状态 #前端

使用示例

示例 1:基础用法

示例 2:高级用法

📄 原始文档

完整文档(英文):

https://skills.sh/supercent-io/skills-template/state-management

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。