首页 / React 19 入门教程 / useReducer

React 19 入门教程

useReducer

本教程共 50 篇 · 第 22 篇 · 更新于 2026-07-29 · 约 7 分钟阅读

ReactuseReducerreducerdispatch状态逻辑

22. useReducer

本节目标:掌握 useReducer 管理复杂状态逻辑,理解 reducer 纯函数,知道什么时候该从 useState 切换到 useReducer。

什么时候该用 useReducer

useState 简单场景够用。但状态逻辑复杂时,代码会变成这样:

function TaskList() {
  const [tasks, setTasks] = useState([]);
  const [editingId, setEditingId] = useState(null);
  const [filter, setFilter] = useState('all');

  function addTask(text) {
    setTasks([...tasks, { id: Date.now(), text, done: false }]);
  }

  function deleteTask(id) {
    setTasks(tasks.filter(t => t.id !== id));
  }

  function toggleTask(id) {
    setTasks(tasks.map(t =>
      t.id === id ? { ...t, done: !t.done } : t
    ));
  }

  // 更多 setState...
}

多个 setState 散落在各处,逻辑不好追踪。useReducer 把它们集中管理。

useReducer 的基本结构

import { useReducer } from 'react';

function TaskList() {
  const [tasks, dispatch] = useReducer(tasksReducer, []);

  function addTask(text) {
    dispatch({ type: 'added', text });
  }

  function deleteTask(id) {
    dispatch({ type: 'deleted', id });
  }

  return (/* ... */);
}

三个要素:

  • reducer:纯函数 (state, action) => newState
  • dispatch:发送 action 的函数
  • action:描述”发生了什么”的对象

写一个 reducer

reducer 是个纯函数,接收当前状态和 action,返回新状态:

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added':
      return [...tasks, {
        id: Date.now(),
        text: action.text,
        done: false
      }];
    case 'deleted':
      return tasks.filter(t => t.id !== action.id);
    case 'toggled':
      return tasks.map(t =>
        t.id === action.id ? { ...t, done: !t.done } : t
      );
    case 'edited':
      return tasks.map(t =>
        t.id === action.id ? { ...t, text: action.text } : t
      );
    default:
      throw Error('未知操作:' + action.type);
  }
}

reducer 必须是纯函数

不修改参数,不调用 API,不做随机数。同样的输入永远返回同样的输出。

完整的待办列表

import { useReducer, useState } from 'react';

function TaskList() {
  const [tasks, dispatch] = useReducer(tasksReducer, []);
  const [text, setText] = useState('');

  function handleAdd() {
    if (!text.trim()) return;
    dispatch({ type: 'added', text });
    setText('');
  }

  return (
    <div>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={handleAdd}>添加</button>
      <ul>
        {tasks.map(task => (
          <li key={task.id}>
            <input
              type="checkbox"
              checked={task.done}
              onChange={() => dispatch({ type: 'toggled', id: task.id })}
            />
            {task.text}
            <button onClick={() => dispatch({ type: 'deleted', id: task.id })}>
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

useState vs useReducer

useStateuseReducer
适用场景简单状态、独立更新复杂逻辑、多个子状态
更新方式直接设值dispatch action
逻辑位置散落在事件处理集中在 reducer
可预测性一般高(所有转换都在 reducer 里)
测试需要渲染组件直接测 reducer 函数

什么时候切换

一个组件里有三个以上相关的 setState,或者 setState 逻辑超过 5 行,考虑 useReducer。

用 Immer 配合 useReducer

reducer 里也要不可变更新,嵌套深了同样痛苦。用 useImmerReducer

import { useImmerReducer } from 'use-immer';

function tasksReducer(tasks, action) {
  switch (action.type) {
    case 'added':
      tasks.push({ id: Date.now(), text: action.text, done: false });
      return;
    case 'toggled':
      const task = tasks.find(t => t.id === action.id);
      task.done = !task.done;
      return;
  }
}

看起来像直接修改,Immer 在背后生成不可变结果。

action 的命名习惯

// 推荐:动词 + 名词
dispatch({ type: 'task_added' });
dispatch({ type: 'task_deleted' });
dispatch({ type: 'user_login' });
dispatch({ type: 'form_reset' });

// 也可以按业务含义
dispatch({ type: 'increment' });
dispatch({ type: 'decrement' });

action 对象没有固定格式,但 { type: 'xxx' } 是约定俗成的。

本节要点

  • 复杂状态逻辑用 useReducer 集中管理
  • reducer 是纯函数 (state, action) => newState
  • dispatch action 描述”发生了什么”
  • 简单场景用 useState,复杂场景用 useReducer