Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

hook:useReducer

  • 可替代 useState 的高级方案,管理复杂的状态变量
  • 适用场景: 状态逻辑复杂、涉及多个子值、下一个状态依赖之前的状态
  • 核心思想: 借鉴 Redux 的 state 管理模式:(state, action) => newState

基本语法

const [state, dispatch] = useReducer(reducer, initialState);

// reducer 函数:接收当前状态和 action,返回新状态
function reducer(state, action) {
  switch (action.type) {
    case "ACTION_TYPE":
      return newState;
    default:
      return state;
  }
}

// 触发状态更新
dispatch({ type: "ACTION_TYPE", payload: data });

场景代码示例

  • 【场景 1】简单计数器(基础):

    • 用 reducer 管理单一状态

      import { useReducer } from "react";
      
      // 定义 reducer
      const reducer = (state, action) => {
        switch (action.type) {
          case "increment":
            return { count: state.count + 1 };
          case "decrement":
            return { count: state.count - 1 };
          case "reset":
            return { count: 0 };
          default:
            return state;
        }
      };
      
      const Counter = () => {
        const [state, dispatch] = useReducer(reducer, { count: 0 });
      
        return (
          <div>
            <p>You clicked {state.count} times</p>
            <button onClick={() => dispatch({ type: "increment" })}>+</button>
            <button onClick={() => dispatch({ type: "decrement" })}>-</button>
            <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
          </div>
        );
      };
      
    • 对比 useState: 这种简单场景用 useState 更简洁,但 useReducer 让逻辑更集中

  • 【场景 2】复杂表单管理(常用):

    • 管理多个输入字段的表单状态

      // 定义 reducer
      const formReducer = (state, action) => {
        switch (action.type) {
          case "SET_FIELD":
            return {
              ...state,
              [action.field]: action.value,
            };
          case "RESET_FORM":
            return action.initialState;
          case "SUBMIT":
            return {
              ...state,
              isSubmitting: true,
            };
          default:
            return state;
        }
      };
      
      const UserForm = () => {
        const initialState = {
          name: "",
          email: "",
          age: "",
          isSubmitting: false,
        };
      
        const [state, dispatch] = useReducer(formReducer, initialState);
      
        const handleChange = (field) => (e) => {
          dispatch({
            type: "SET_FIELD",
            field,
            value: e.target.value,
          });
        };
      
        const handleSubmit = () => {
          dispatch({ type: "SUBMIT" });
          // 提交逻辑...
        };
      
        return (
          <form>
            <input
              value={state.name}
              onChange={handleChange("name")}
              placeholder="姓名"
            />
            <input
              value={state.email}
              onChange={handleChange("email")}
              placeholder="邮箱"
            />
            <input
              value={state.age}
              onChange={handleChange("age")}
              placeholder="年龄"
            />
            <button onClick={handleSubmit} disabled={state.isSubmitting}>
              {state.isSubmitting ? "提交中..." : "提交"}
            </button>
            <button
              onClick={() => dispatch({ type: "RESET_FORM", initialState })}
            >
              重置
            </button>
          </form>
        );
      };
      
    • 优势: 统一管理多个字段,逻辑清晰,易于测试

  • 【场景 3】待办列表(常用):

    • 管理列表的增删改查

      // 定义 reducer
      const todoReducer = (state, action) => {
        switch (action.type) {
          case "ADD_TODO":
            return {
              ...state,
              todos: [
                ...state.todos,
                { id: Date.now(), text: action.text, completed: false },
              ],
            };
          case "TOGGLE_TODO":
            return {
              ...state,
              todos: state.todos.map((todo) =>
                todo.id === action.id
                  ? { ...todo, completed: !todo.completed }
                  : todo
              ),
            };
          case "DELETE_TODO":
            return {
              ...state,
              todos: state.todos.filter((todo) => todo.id !== action.id),
            };
          case "SET_FILTER":
            return {
              ...state,
              filter: action.filter,
            };
          default:
            return state;
        }
      };
      
      const TodoApp = () => {
        const [state, dispatch] = useReducer(todoReducer, {
          todos: [],
          filter: "all", // 'all' | 'active' | 'completed'
        });
      
        const [input, setInput] = useState("");
      
        const addTodo = () => {
          if (input.trim()) {
            dispatch({ type: "ADD_TODO", text: input });
            setInput("");
          }
        };
      
        return (
          <div>
            <input value={input} onChange={(e) => setInput(e.target.value)} />
            <button onClick={addTodo}>添加</button>
      
            <div>
              <button
                onClick={() => dispatch({ type: "SET_FILTER", filter: "all" })}
              >
                全部
              </button>
              <button
                onClick={() => dispatch({ type: "SET_FILTER", filter: "active" })}
              >
                未完成
              </button>
              <button
                onClick={() =>
                  dispatch({ type: "SET_FILTER", filter: "completed" })
                }
              >
                已完成
              </button>
            </div>
      
            <ul>
              {state.todos.map((todo) => (
                <li key={todo.id}>
                  <input
                    type="checkbox"
                    checked={todo.completed}
                    onChange={() =>
                      dispatch({ type: "TOGGLE_TODO", id: todo.id })
                    }
                  />
                  <span
                    style={{
                      textDecoration: todo.completed ? "line-through" : "none",
                    }}
                  >
                    {todo.text}
                  </span>
                  <button
                    onClick={() => dispatch({ type: "DELETE_TODO", id: todo.id })}
                  >
                    删除
                  </button>
                </li>
              ))}
            </ul>
          </div>
        );
      };
      
    • 优势: 多种操作集中管理,状态更新逻辑清晰

  • 【场景 4】带 payload 的 action(常用):

    • action 携带额外数据

      // 定义 reducer
      const counterReducer = (state, action) => {
        switch (action.type) {
          case "INCREMENT":
            return { count: state.count + (action.payload || 1) };
          case "DECREMENT":
            return { count: state.count - (action.payload || 1) };
          case "SET":
            return { count: action.payload };
          default:
            return state;
        }
      };
      
      const Counter = () => {
        const [state, dispatch] = useReducer(counterReducer, { count: 0 });
      
        return (
          <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
            <button onClick={() => dispatch({ type: "INCREMENT", payload: 5 })}>
              +5
            </button>
            <button onClick={() => dispatch({ type: "INCREMENT", payload: 10 })}>
              +10
            </button>
            <button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
            <button onClick={() => dispatch({ type: "SET", payload: 100 })}>
              设为 100
            </button>
          </div>
        );
      };
      
    • 优势: action 可以携带任意数据,更加灵活

  • 【场景 5】配合 Context 使用(最佳实践):

    • 在全局状态中使用 useReducer

      import { createContext, useReducer, useContext } from "react";
      
      // 定义 reducer
      const appReducer = (state, action) => {
        switch (action.type) {
          case "SET_USER":
            return { ...state, user: action.payload };
          case "SET_THEME":
            return { ...state, theme: action.payload };
          case "LOGOUT":
            return { ...state, user: null };
          default:
            return state;
        }
      };
      
      // 创建 Context
      const AppContext = createContext(null);
      
      // Provider 组件
      export const AppProvider = ({ children }) => {
        const [state, dispatch] = useReducer(appReducer, {
          user: null,
          theme: "light",
        });
      
        return (
          <AppContext.Provider value={{ state, dispatch }}>
            {children}
          </AppContext.Provider>
        );
      };
      
      // 自定义 Hook
      export const useApp = () => {
        const context = useContext(AppContext);
        if (!context) {
          throw new Error("useApp 必须在 AppProvider 内部使用");
        }
        return context;
      };
      
      // 在组件中使用
      const Profile = () => {
        const { state, dispatch } = useApp();
      
        return (
          <div>
            <p>用户: {state.user?.name || "未登录"}</p>
            <p>主题: {state.theme}</p>
            <button
              onClick={() =>
                dispatch({ type: "SET_USER", payload: { name: "Alice" } })
              }
            >
              登录
            </button>
            <button onClick={() => dispatch({ type: "LOGOUT" })}>退出</button>
            <button
              onClick={() =>
                dispatch({
                  type: "SET_THEME",
                  payload: state.theme === "light" ? "dark" : "light",
                })
              }
            >
              切换主题
            </button>
          </div>
        );
      };
      
    • 优势: 结合 Context,实现类似 Redux 的全局状态管理,但更轻量

  • 【场景 6】惰性初始化(优化):

    • 初始状态需要复杂计算时,使用第三个参数

      // 初始化函数
      const init = (initialCount) => {
        console.log("初始化执行(只执行一次)");
        return { count: initialCount };
      };
      
      const reducer = (state, action) => {
        switch (action.type) {
          case "INCREMENT":
            return { count: state.count + 1 };
          case "RESET":
            return init(action.payload); // 重置时也可以调用 init
          default:
            return state;
        }
      };
      
      const Counter = ({ initialCount }) => {
        // 第三个参数:惰性初始化函数
        const [state, dispatch] = useReducer(reducer, initialCount, init);
      
        return (
          <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
            <button onClick={() => dispatch({ type: "RESET", payload: 0 })}>
              Reset
            </button>
          </div>
        );
      };
      
    • 适用场景: 从 localStorage 读取、复杂计算、解析大型数据


useReducer vs useState

特性useStateuseReducer
适用场景简单状态复杂状态、多个子值
状态更新逻辑分散在组件中集中在 reducer 中
可测试性较难测试reducer 是纯函数,易于测试
代码可维护性简单场景更直观复杂场景更清晰
学习成本中等(需要理解 reducer 概念)
性能优化需要多次调用 setState一次 dispatch,更新多个值
适合团队协作一般更好(逻辑统一)

选择原则:

  • 简单的独立状态(如单个数字、字符串、布尔值)→ 用 useState
  • 复杂的状态对象、多个相关状态、复杂更新逻辑 → 用 useReducer

何时使用 useReducer

情况说明示例
状态逻辑复杂多个子值、多种操作表单、待办列表
下一个状态依赖之前的状态需要基于当前状态计算新状态计数器的增减
多个相关状态需要同时更新避免多次 setState用户信息的多字段更新
想要集中管理状态更新逻辑便于维护和测试复杂的业务逻辑
需要在回调中触发深层更新通过 dispatch 传递给子组件配合 Context 使用
状态更新逻辑可能复用reducer 可以提取到单独文件多个组件共享相同逻辑

Action 的最佳实践

  • 使用常量定义 action type

    // ✅ 推荐:使用常量,避免拼写错误
    const ActionTypes = {
      INCREMENT: "INCREMENT",
      DECREMENT: "DECREMENT",
      RESET: "RESET",
    };
    
    const reducer = (state, action) => {
      switch (action.type) {
        case ActionTypes.INCREMENT:
          return { count: state.count + 1 };
        case ActionTypes.DECREMENT:
          return { count: state.count - 1 };
        default:
          return state;
      }
    };
    
    // 使用时
    dispatch({ type: ActionTypes.INCREMENT });
    
  • 使用 Action Creator 函数

    // ✅ 推荐:封装 action 创建逻辑
    const actions = {
      increment: (amount = 1) => ({ type: "INCREMENT", payload: amount }),
      decrement: (amount = 1) => ({ type: "DECREMENT", payload: amount }),
      reset: () => ({ type: "RESET" }),
    };
    
    // 使用时
    dispatch(actions.increment(5));
    dispatch(actions.decrement());
    
  • TypeScript 类型定义

    // 定义 State 类型
    interface State {
      count: number;
      loading: boolean;
    }
    
    // 定义 Action 类型
    type Action =
      | { type: "INCREMENT"; payload?: number }
      | { type: "DECREMENT"; payload?: number }
      | { type: "SET_LOADING"; payload: boolean }
      | { type: "RESET" };
    
    // Reducer 函数
    const reducer = (state: State, action: Action): State => {
      switch (action.type) {
        case "INCREMENT":
          return { ...state, count: state.count + (action.payload ?? 1) };
        case "DECREMENT":
          return { ...state, count: state.count - (action.payload ?? 1) };
        case "SET_LOADING":
          return { ...state, loading: action.payload };
        case "RESET":
          return { count: 0, loading: false };
        default:
          return state;
      }
    };
    

注意事项

  • ⚠️ Reducer 必须是纯函数

    // ❌ 错误:有副作用
    const badReducer = (state, action) => {
      console.log("状态更新"); // 副作用:日志
      localStorage.setItem("count", state.count); // 副作用:存储
      return { count: state.count + 1 };
    };
    
    // ✅ 正确:纯函数,无副作用
    const goodReducer = (state, action) => {
      return { count: state.count + 1 };
    };
    
    // 副作用应该在 useEffect 中处理
    useEffect(() => {
      console.log("状态更新");
      localStorage.setItem("count", state.count);
    }, [state.count]);
    
  • ⚠️ 不要直接修改 state

    // ❌ 错误:直接修改原对象
    const badReducer = (state, action) => {
      state.count += 1; // 不会触发重新渲染
      return state;
    };
    
    // ✅ 正确:返回新对象
    const goodReducer = (state, action) => {
      return { ...state, count: state.count + 1 };
    };
    
  • ⚠️ default 分支要返回原 state

    const reducer = (state, action) => {
      switch (action.type) {
        case "INCREMENT":
          return { count: state.count + 1 };
        default:
          return state; // 必须返回原状态
      }
    };
    
  • 复杂 reducer 可以拆分

    // 拆分为多个 reducer
    const userReducer = (state, action) => {
      /* ... */
    };
    const themeReducer = (state, action) => {
      /* ... */
    };
    
    // 组合 reducer
    const rootReducer = (state, action) => {
      return {
        user: userReducer(state.user, action),
        theme: themeReducer(state.theme, action),
      };
    };
    
  • 使用 TypeScript 获得类型安全

    // 定义清晰的类型,避免拼写错误和类型错误
    type Action =
      | { type: "INCREMENT" }
      | { type: "DECREMENT" }
      | { type: "SET"; payload: number };