你可能不需要 Redux:使用 React Context + useReducer hook

2025-06-10

你可能不需要 Redux:使用 React Context + useReducer hook

我想修改这一点:除非你遇到 vanilla React 的问题,否则不要使用 Redux。—— Dan Abramov

Dan 早在 2016 年就说过,现在我们有了 React Context 和 useReducer hook,redux 的用例就非常少了。在这篇文章中,我们将使用 Context 和 useReducer hook 创建一个经典的待办事项列表示例。

首先,让我们设置初始状态和操作。让我们的待办事项应用包含三个操作:添加、删除和切换完成状态。

const initialState = {
  todoList: []
};

const actions = {
  ADD_TODO_ITEM: "ADD_TODO_ITEM",
  REMOVE_TODO_ITEM: "REMOVE_TODO_ITEM",
  TOGGLE_COMPLETED: "TOGGLE_COMPLETED"
};
Enter fullscreen mode Exit fullscreen mode

现在让我们添加一个 reducer 函数来处理我们的操作。

const reducer = (state, action) => {
  switch (action.type) {
    case actions.ADD_TODO_ITEM:
      return {
        todoList: [
          ...state.todoList,
          {
            id: new Date().valueOf(),
            label: action.todoItemLabel,
            completed: false
          }
        ]
      };
    case actions.REMOVE_TODO_ITEM: {
      const filteredTodoItem = state.todoList.filter(
        (todoItem) => todoItem.id !== action.todoItemId
      );
      return { todoList: filteredTodoItem };
    }
    case actions.TOGGLE_COMPLETED: {
      const updatedTodoList = state.todoList.map((todoItem) =>
        todoItem.id === action.todoItemId
          ? { ...todoItem, completed: !todoItem.completed }
          : todoItem
      );
      return { todoList: updatedTodoList };
    }
    default:
      return state;
  }
};
Enter fullscreen mode Exit fullscreen mode

让我们来分析一下。

  • 在操作中,我扩展了现有列表,并使用(唯一)、(用户输入的值)和标志ADD_TODO_ITEM向列表中添加了新的待办事项idlabelcompleted
  • REMOVE_TODO_ITEM操作中,我根据 ID 过滤掉需要删除的待办事项。
  • TOGGLE_COMPLETED操作中,我循环遍历所有待办事项并根据 ID 切换已完成标志。

现在,让我们将它们与 Context 和 useReducer 连接起来。让我们创建一个TodoListContext

const TodoListContext = React.createContext();
Enter fullscreen mode Exit fullscreen mode

让我们创建一个Provider返回我们TodoListContext的提供程序的函数。

const Provider = ({ children }) => {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  const value = {
    todoList: state.todoList,
    addTodoItem: (todoItemLabel) => {
      dispatch({ type: actions.ADD_TODO_ITEM, todoItemLabel });
    },
    removeTodoItem: (todoItemId) => {
      dispatch({ type: actions.REMOVE_TODO_ITEM, todoItemId });
    },
    markAsCompleted: (todoItemId) => {
      dispatch({ type: actions.TOGGLE_COMPLETED, todoItemId });
    }
  };

  return (
    <TodoListContext.Provider value={value}>
      {children}
    </TodoListContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

让我们来分析一下。

  • 我们将reducer函数和我们的传递initialState给 useReducer hook。这将返回状态和调度。状态将包含我们的 initialState,调度用于触发我们的操作,就像在 Redux 中一样。
  • 在值对象中,我们有 todoList 状态,以及三个函数addTodoItemremoveTodoItem、 和,markAsCompleted它们分别触发ADD_TODO_ITEMREMOVE_TODO_ITEM、 和TOGGLE_COMPLETED动作。
  • 我们将值对象作为 prop 传递给 的TodoListContext提供程序,以便我们可以使用 访问它useContext

太好了,现在我们的全局 store 和 reducer 都设置好了。现在让我们创建两个组件AddTodoTodoList它们将使用我们的 store。

const AddTodo = () => {
  const [inputValue, setInputValue] = React.useState("");
  const { addTodoItem } = React.useContext(TodoListContext);

  return (
    <>
      <input
        type="text"
        value={inputValue}
        placeholder={"Type and add todo item"}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button
        onClick={() => {
          addTodoItem(inputValue);
          setInputValue("");
        }}
      >
        Add
      </button>
    </>
  );
};
Enter fullscreen mode Exit fullscreen mode

在 中AddTodo,我们使用 useContext 订阅我们的TodoListContextdispatchaddTodoItem函数。该组件有一个输入字段,用户可以在其中输入待办事项,还有一个add按钮,用于将待办事项添加到列表中。

const TodoList = () => {
  const { todoList, removeTodoItem, markAsCompleted } = React.useContext(
    TodoListContext
  );
  return (
    <ul>
      {todoList.map((todoItem) => (
        <li
          className={todoItem.completed ? "completed" : ""}
          key={todoItem.id}
          onClick={() => markAsCompleted(todoItem.id)}
        >
          {todoItem.label}
          <button
            className="delete"
            onClick={() => removeTodoItem(todoItem.id)}
          >
            X
          </button>
        </li>
      ))}
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

TodoList组件中,我们使用 useContext 来订阅TodoListContext并获取todoList状态、removeTodoItem以及andmarkAsCompleteddispatch 函数。我们通过 进行映射todoList,渲染待办事项以及其旁边的 remove(X) 按钮。点击某个事项时,我们会将其标记为 ;complete点击X按钮时,我们会将其从列表中移除。

最后,让我们用我们的提供程序包装我们的两个组件。

export default function App() {
  return (
    <Provider>
      <AddTodo />
      <TodoList />
    </Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

太棒了!我们使用了 Context 和 useReducer hook 来管理状态,作为 redux 的替代方案。你可以在codesandbox查看实际代码。

好了,各位,感谢阅读这篇博文。希望它对你有所帮助。欢迎留言提出你的问题和建议。

鏂囩珷鏉ユ簮锛�https://dev.to/nikhilkumaran/you-probously-don-t-need-redux-use-react-context-usereducer-hook-55ej
PREV
给 Web 开发者新手的 9 个实用技巧 GenAI LIVE! | 2025 年 6 月 4 日
NEXT
使用 HTML 和 CSS 实现响应式导航栏