🚀 React 状态管理:useReducer + useContext 魔法组合

120 阅读7分钟

📚 目录

  1. 第一章:useReducer 是谁?它为什么这么酷?
  2. 第二章:useContext 是快递员,把状态送到每个角落
  3. 第三章:useReducer + useContext = 全局状态管理神器
  4. 第四章:自定义 Hook 让代码复用像搭积木
  5. 第五章:总结与进阶思考

image.png

🧠 第一章:useReducer 是谁?它为什么这么酷? 😄

image.png

想象一下,你的代码世界里有一个严格的管家,它叫 useReducer。这个管家的工作是:

  1. 接收指令(action) :比如 "添加任务"、"删除任务"
  2. 执行操作:根据指令修改状态(如 todos 列表)
  3. 返回新状态:确保状态变更可预测、可追溯 image.png

🚀 为什么 useReducer 比 useState 更酷?

  • 适合复杂逻辑:比如管理多个状态(如 todos、用户权限、主题切换)。
  • 纯函数设计:状态变更规则清晰,避免副作用。
  • 性能优化:通过 dispatch 向下传递,减少不必要的渲染。

🧪 代码示例:useReducer 的 reducer 函数

// reducers/todoReducer.js
function todoReducer(state, action) {
  switch (action.type) {
    case "ADD_TODO":
      return [
        ...state,
        {
          id: Date.now(),         // 唯一ID(用时间戳生成)
          text: action.payload,   // 从action中获取任务内容
          completed: false,       // 默认未完成
        },
      ];
    case "REMOVE_TODO":
      return state.filter((todo) => todo.id !== action.payload); // 过滤掉指定ID的任务
    case "TOGGLE_TODO":
      return state.map((todo) =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed } // 切换完成状态
          : todo
      );
    case "CLEAR_TODOS":
      return []; // 清空所有任务
    default:
      return state; // 默认返回原状态
  }
}

🧠 逐行注释

  • ADD_TODO:添加新任务时,使用 Date.now() 生成唯一 ID,避免重复。
  • REMOVE_TODO:通过 filter 方法移除指定 ID 的任务,保持不可变性。
  • TOGGLE_TODO:使用 map 遍历数组,仅修改匹配 ID 的任务状态,其余保持不变。
  • CLEAR_TODOS:直接返回空数组,清空所有任务。

😅 幽默提示
useReducer 像一个不讲情面的管家,只听 action 的指令,绝不偷偷摸摸改状态!


🚚 第二章:useContext 是快递员,把状态送到每个角落 📦

image.png

如果 useReducer 是管家,那么 useContext 就是快递员。它的任务是:

  1. 创建上下文:通过 createContext 定义一个全局变量。
  2. 提供状态:用 Provider 将状态包裹在组件树中。
  3. 消费状态:用 useContext 在任意组件中获取状态。 image.png

🧪 代码示例:useContext 的使用

// TodoContext.js
import { createContext } from "react";
export const TodoContext = createContext(null); // 创建上下文
// App.jsx
import { TodoContext } from "./TodoContext";
import { useTodos } from "./hooks/useTodos";
function App() {
  const todosHook = useTodos([]); // 初始化状态
  return (
    <TodoContext.Provider value={todosHook}>
      {/* 子组件会自动接收到 todosHook */}
      <AddTodo />
      <TodoList />
    </TodoContext.Provider>
  );
}

🧠 逐行注释

  • createContext(null):创建一个初始值为 null 的上下文,作为全局状态的“快递站”。
  • Provider:将 todosHook 作为值传递给所有子组件,无论组件层级多深,都能“签收”状态。

😄 幽默提示
useContext 是快递员,把状态送到每个需要的组件,再也不用 props 层层传递啦!📦


🔁 第三章:useReducer + useContext = 全局状态管理神器 🌟

image.png

现在,我们将 useReduceruseContext 结合,打造一个跨层级的全局状态管理方案。想象一下:

  • AddTodo 组件:添加任务时,直接调用 addTodo
  • TodoList 组件:展示任务列表,点击切换状态或删除任务。
  • 无需 props 传递:所有组件通过 Context 获取状态和操作函数。 image.png

❤️ 结合的核心作用

功能useReducer 贡献useContext 贡献
状态管理通过 reducer 函数集中管理状态变更逻辑提供全局状态访问,无需 props 传递
跨组件通信定义统一的 action 类型和操作函数任何组件都可以直接访问全局状态和操作函数
性能优化避免重复渲染,仅在状态变化时更新减少中间组件的 props 传递,降低渲染层级

🧪 代码示例:useReducer + useContext 结合

✅ 自定义 Hook:useTodos

// hooks/useTodos.js
import { useReducer } from "react";
import todoReducer from "../reducers/todoReducer";

export function useTodos(initial = []) {
  const [todos, dispatch] = useReducer(todoReducer, initial);

  // 封装操作函数
  const addTodo = (text) => dispatch({ type: "ADD_TODO", payload: text });
  const toggleTodo = (id) => dispatch({ type: "TOGGLE_TODO", payload: id });
  const clearTodos = () => dispatch({ type: "CLEAR_TODOS" });
  const removeTodo = (id) => dispatch({ type: "REMOVE_TODO", payload: id });

  return { todos, addTodo, removeTodo, toggleTodo, clearTodos };
}

🧠 逐行注释

  • useReducer(todoReducer, initial):初始化状态,initial 是初始的 todos 数组。
  • addTodo:封装 dispatch 调用,触发 ADD_TODO action,添加新任务。
  • toggleTodo:封装 dispatch 调用,触发 TOGGLE_TODO action,切换任务状态。

✅ AddTodo 组件

// components/AddTodo.jsx
import { useState } from "react";
import { useTodoContext } from "../hooks/useTodoContext";

const AddTodo = () => {
  const [text, setText] = useState("");
  const { addTodo } = useTodoContext(); // 从 Context 获取 addTodo 函数

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      addTodo(text.trim()); // 调用 addTodo,无需手动管理状态
      setText("");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <button type="submit">Add Todo</button>
    </form>
  );
};

🧠 逐行注释

  • useTodoContext():从 Context 中获取 addTodo 函数,无需手动传递 props。
  • addTodo(text.trim()):调用封装好的函数,触发 reducer 更新状态。

✅ TodoList 组件

// components/TodoList.jsx
import { useTodoContext } from "../hooks/useTodoContext";

const TodoList = () => {
  const { todos, toggleTodo, removeTodo, clearTodos } = useTodoContext(); // 从 Context 获取所有操作

  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          <span
            onClick={() => toggleTodo(todo.id)} // 点击切换任务状态
            style={{ textDecoration: todo.completed ? "line-through" : "none" }}
          >
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>Remove</button> {/* 删除单个任务 */}
        </li>
      ))}
      <button onClick={clearTodos}>Clear All</button> {/* 清空所有任务 */}
    </ul>
  );
};

🧠 逐行注释

  • useTodoContext():从 Context 中获取 todos 列表和所有操作函数。
  • toggleTodo(todo.id):点击任务时,直接调用封装好的函数,切换状态。
  • removeTodo(todo.id):删除指定 ID 的任务,无需手动管理状态。

🎉 总结
通过 useReducer + useContext,我们实现了跨组件、跨层级的全局状态管理,再也不用担心 props 层层传递的麻烦!


🧩 第四章:自定义 Hook 让代码复用像搭积木 🛠️

image.png

❤️ 自定义 Hook 的优势

自定义 Hook 是 React 的超级武器,它可以将逻辑封装成可复用的模块。比如:

  • 封装逻辑:将状态管理逻辑集中到 Hook 中,组件只需调用即可。
  • 提升复用性:多个组件可以共享同一个 Hook,避免重复代码。
  • 降低复杂度:组件只关注 UI 渲染,状态逻辑由 Hook 处理。
  • useTodo:封装了所有与 Todo 相关的状态和操作。
  • useTodoContext:简化了 Context 的使用,避免重复代码。 image.png

🧪 代码示例:自定义 Hook 的优势

✅ useTodoContext Hook

// hooks/useTodoContext.js
import { useContext } from "react";
import { TodoContext } from "../TodoContext";

export function useTodoContext() {
  return useContext(TodoContext); // 简化 Context 使用
}

🧠 逐行注释

  • useContext(TodoContext):直接返回 Context 的值,组件无需手动调用 useContext

😄 幽默提示
自定义 Hook 像是搭积木,把复杂的逻辑拆分成小模块,拼起来就是完整的功能!🧩


🌈 第五章:总结与进阶思考 🚀

image.png

✅ 总结

  • useReducer:适合管理复杂状态,通过纯函数确保状态变更的可预测性。
  • useContext:实现全局状态共享,避免 props 层层传递。
  • 自定义 Hook:提升代码复用性和可维护性,让逻辑更清晰。

🧠 进阶思考

  1. 持久化状态:将 todos 存储到 localStorage,刷新页面数据不丢失。
  2. 扩展功能:支持任务编辑、优先级分类、分类筛选等功能。
  3. 性能优化:结合 useMemouseCallback,避免不必要的渲染。

🎉 最后的小彩蛋

如果你觉得这篇文章对你有帮助,不妨点个赞!🌟
或者分享给你的朋友,一起学习 React 的魔法组合吧!🎉


📌 附录:完整代码示例

✅ 项目结构

src/
  ├── App.jsx                // 应用入口
  ├── TodoContext.js         // 创建上下文
  ├── components/
  │   ├── AddTodo.jsx        // 新增 Todo 输入组件
  │   └── TodoList.jsx       // Todo 列表组件
  ├── hooks/
  │   ├── useTodoContext.js  // 自定义 Hook,简化 Context 使用
  │   └── useTodos.js        // 自定义 Hook,封装 useReducer 逻辑
  └── reducers/
      └── todoReducer.js     // reducer 纯函数

🧪 App.jsx

import { TodoContext } from "./TodoContext";
import { useTodos } from "./hooks/useTodos";
import AddTodo from "./components/AddTodo";
import TodoList from "./components/TodoList";

function App() {
  const todosHook = useTodos([]);
  return (
    <TodoContext.Provider value={todosHook}>
      <h1>📝 My Todos</h1>
      <AddTodo />
      <TodoList />
    </TodoContext.Provider>
  );
}

export default App;

🎈 TodoContext.js

import { createContext } from "react";
export const TodoContext = createContext(null);

🎈 todoReducer.js

function todoReducer(state, action) {
  switch (action.type) {
    case "ADD_TODO":
      return [
        ...state,
        {
          id: Date.now(),
          text: action.payload,
          completed: false,
        },
      ];
    case "REMOVE_TODO":
      return state.filter((todo) => todo.id !== action.payload);
    case "TOGGLE_TODO":
      return state.map((todo) =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    case "CLEAR_TODOS":
      return [];
    default:
      return state;
  }
}

export default todoReducer;

🧪 AddTodo.jsx

import {
  useState, // 私有
} from "react";
import { useTodoContext } from "../hooks/useTodoContext";

const AddTodo = () => {
  const [text, setText] = useState("");
  const { addTodo } = useTodoContext();// 跨层级
  const handleSubmit = (e) => {
    e.preventDefault();
    // 全局管理
    if (text.trim()) {
      addTodo(text.trim());
      setText("");
    }
  };
  return (
    <form action="" onSubmit={handleSubmit}>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <button type="submit">Add Todo</button>
    </form>
  );
};

export default AddTodo;

🧪 TodoList.jsx

import { useTodoContext } from "../hooks/useTodoContext";

const TodoList = () => {
  const { todos, toggleTodo, removeTodo, clearTodos } = useTodoContext();
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          <span
            onClick={() => toggleTodo(todo.id)}
            style={{ textDecoration: todo.completed ? "line-through" : "none" }}
          >
            {todo.text}
          </span>
          <button onClick={() => removeTodo(todo.id)}>Remove</button>
          <button onClick={() => clearTodos()}>Clear</button>
        </li>
      ))}
    </ul>
  );
};

export default TodoList;

🎈 useTodoContext.js

import {
    useContext
} from "react";
import { TodoContext } from "../TodoContext";

// 自定义Hooks
export function useTodoContext() {
  return useContext(TodoContext);
}

🎈 useTodos.js

import { useReducer } from "react";
import todoReducer from "../reducers/todoReducer";
// ES6 参数的默认值
// {todos,} key:value 省略
// ``模板字符串
// 解构 [] = [] {} = {}
// 展开运算符,...rest 运算符
export function useTodos(initial = []) {
  const [todos, dispatch] = useReducer(todoReducer, initial);
  const addTodo = (text) => {
    dispatch({ type: "ADD_TODO", payload: text });
  };
  const toggleTodo = (id) => {
    dispatch({ type: "TOGGLE_TODO", payload: id });
  };
  const clearTodos = () => {
    dispatch({ type: "CLEAR_TODOS" });
  };
  const removeTodo = (id) => {
    dispatch({ type: "REMOVE_TODO", payload: id });
  };
  return {
    todos,
    addTodo,
    removeTodo,
    toggleTodo,
    clearTodos,
  };
}

📚 参考资料


🎉 感谢阅读!希望这篇文章能让你对 React 的状态管理有更深入的理解。如果有任何问题或建议,欢迎留言讨论!