📚 目录
- 第一章:useReducer 是谁?它为什么这么酷?
- 第二章:useContext 是快递员,把状态送到每个角落
- 第三章:useReducer + useContext = 全局状态管理神器
- 第四章:自定义 Hook 让代码复用像搭积木
- 第五章:总结与进阶思考
🧠 第一章:useReducer 是谁?它为什么这么酷? 😄
想象一下,你的代码世界里有一个严格的管家,它叫
useReducer。这个管家的工作是:
- 接收指令(action) :比如 "添加任务"、"删除任务"
- 执行操作:根据指令修改状态(如 todos 列表)
- 返回新状态:确保状态变更可预测、可追溯
🚀 为什么 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 是快递员,把状态送到每个角落 📦
如果
useReducer是管家,那么useContext就是快递员。它的任务是:
- 创建上下文:通过
createContext定义一个全局变量。- 提供状态:用
Provider将状态包裹在组件树中。- 消费状态:用
useContext在任意组件中获取状态。
🧪 代码示例: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 = 全局状态管理神器 🌟
现在,我们将
useReducer和useContext结合,打造一个跨层级的全局状态管理方案。想象一下:
- AddTodo 组件:添加任务时,直接调用
addTodo。- TodoList 组件:展示任务列表,点击切换状态或删除任务。
- 无需 props 传递:所有组件通过 Context 获取状态和操作函数。
❤️ 结合的核心作用
| 功能 | 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_TODOaction,添加新任务。toggleTodo:封装dispatch调用,触发TOGGLE_TODOaction,切换任务状态。
✅ 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 让代码复用像搭积木 🛠️
❤️ 自定义 Hook 的优势
自定义 Hook 是 React 的超级武器,它可以将逻辑封装成可复用的模块。比如:
- 封装逻辑:将状态管理逻辑集中到 Hook 中,组件只需调用即可。
- 提升复用性:多个组件可以共享同一个 Hook,避免重复代码。
- 降低复杂度:组件只关注 UI 渲染,状态逻辑由 Hook 处理。
- useTodo:封装了所有与 Todo 相关的状态和操作。
- useTodoContext:简化了 Context 的使用,避免重复代码。
🧪 代码示例:自定义 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 像是搭积木,把复杂的逻辑拆分成小模块,拼起来就是完整的功能!🧩
🌈 第五章:总结与进阶思考 🚀
✅ 总结
- useReducer:适合管理复杂状态,通过纯函数确保状态变更的可预测性。
- useContext:实现全局状态共享,避免 props 层层传递。
- 自定义 Hook:提升代码复用性和可维护性,让逻辑更清晰。
🧠 进阶思考
- 持久化状态:将 todos 存储到
localStorage,刷新页面数据不丢失。 - 扩展功能:支持任务编辑、优先级分类、分类筛选等功能。
- 性能优化:结合
useMemo和useCallback,避免不必要的渲染。
🎉 最后的小彩蛋
如果你觉得这篇文章对你有帮助,不妨点个赞!🌟
或者分享给你的朋友,一起学习 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 的状态管理有更深入的理解。如果有任何问题或建议,欢迎留言讨论!