🔥 再也不用 props 疯狂套娃了!React 状态管理,不需要 Redux 也能很香。
🧭 导语:当 useState 不再够用
刚入门 React 时,一切看起来都很简单:
用 useState 管状态,组件里写点逻辑,网页就动起来了,嘎嘎香。
可当你做个稍微复杂点的功能,比如:
- 购物车里的商品数量,多个组件都要用;
- 登录状态,顶部菜单、个人页都得感知;
- 页面主题,暗黑 or 明亮,全站同步切换;
这时候你可能就绷不住了:
👉🏻 状态只能一级一级地传 props,A 传给 B,B 再传给 C,C 再给 D……
👉🏻 某个状态被很多组件依赖,一改就一片重渲染,调 bug 疯狂。
所以今天我们来学习一个轻量但专业的方案:
🧩 一套组合拳:useReducer + useContext + 自定义 Hook
它的核心目标是:
💡 提取出全局共享的状态逻辑,避免 props 传递地狱,让组件之间自由访问共享状态。
下面是它们的分工:
useReducer: 管理状态结构 + 状态更新逻辑(就像 Redux)useContext: 构建一个“状态仓库”,供全局访问- 自定义 Hook:做一层封装,隐藏复杂逻辑,暴露易用 API
接下来我们一步步构建一个「待办事项(Todo List)」应用,来实践这套组合方案。
✅ 场景目标:Todo 列表功能拆解
我们希望实现的功能:
- 新增一条待办事项
- 标记事项为完成/未完成
- 删除一项事项
- 多个组件都能共享操作这份 Todo 列表
🛠 项目结构 & 技术拆解
- 状态形态:
todos是一个数组,包含{ id, text, done }的对象 - 状态更新:统一走
reducer函数 - 状态共享:基于
Context - 状态调用:封装到
Hook
🔧 第一步:创建 Context 容器
// context/TodoContext.js
import { createContext } from 'react';
export const TodoContext = createContext(null);
这就是我们状态“仓库”的壳子,稍后我们会把状态和操作都挂进来。
🧠 第二步:定义 reducer(状态逻辑规则)
// reducers/todoReducer.js
export default function todoReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, { id: Date.now(), text: action.text, done: false }];
case 'TOGGLE':
return state.map(item =>
item.id === action.id ? { ...item, done: !item.done } : item
);
case 'DELETE':
return state.filter(item => item.id !== action.id);
default:
return state;
}
}
📌 解读:
- 状态是不可变的,所有操作都返回新数组;
- 操作必须明确:通过 type 和 payload 来驱动。
🧰 第三步:封装 useTodos Hook(核心业务封装)
// hooks/useTodos.js
import { useReducer } from 'react';
import todoReducer from '../reducers/todoReducer';
export function useTodos(initial = []) {
const [todos, dispatch] = useReducer(todoReducer, initial);
const add = (text) => dispatch({ type: 'ADD', text });
const toggle = (id) => dispatch({ type: 'TOGGLE', id });
const del = (id) => dispatch({ type: 'DELETE', id });
return { todos, add, toggle, del };
}
优势:
- 所有操作都内聚在这里;
- 不暴露 dispatch,外部不能乱操作状态;
- 实际组件里调用极其清爽。
🪝 第四步:封装 useTodoContext(简化访问)
// hooks/useTodoContext.js
import { useContext } from 'react';
import { TodoContext } from '../context/TodoContext';
export const useTodoContext = () => useContext(TodoContext);
使用方式:
const { todos, add, toggle, del } = useTodoContext();
简洁、直观。
🧱 第五步:App 根组件绑定 Provider
// App.jsx
import { TodoContext } from './context/TodoContext';
import { useTodos } from './hooks/useTodos';
import AddTodo from './components/AddTodo';
import TodoList from './components/TodoList';
export default function App() {
const todoLogic = useTodos([]);
return (
<TodoContext.Provider value={todoLogic}>
<h2>My Todos</h2>
<AddTodo />
<TodoList />
</TodoContext.Provider>
);
}
Provider 是整个状态系统的入口,所有子组件都可以访问它共享的状态。
➕ 第六步:新增 Todo 的组件
// components/AddTodo.jsx
import { useState } from 'react';
import { useTodoContext } from '../hooks/useTodoContext';
export default function AddTodo() {
const [input, setInput] = useState('');
const { add } = useTodoContext();
const handleAdd = (e) => {
e.preventDefault();
if (input.trim()) {
add(input.trim());
setInput('');
}
};
return (
<form onSubmit={handleAdd}>
<input value={input} onChange={e => setInput(e.target.value)} />
<button type="submit">Add</button>
</form>
);
}
📋 第七步:展示 Todo 列表的组件
// components/TodoList.jsx
import { useTodoContext } from '../hooks/useTodoContext';
export default function TodoList() {
const { todos, toggle, del } = useTodoContext();
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<span
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
onClick={() => toggle(todo.id)}
>
{todo.text}
</span>
<button onClick={() => del(todo.id)}>Delete</button>
</li>
))}
</ul>
);
}
🧠 总结:这一套值不值得用?
✅ 优点:
- 组织清晰:状态逻辑集中管理,行为明确。
- 完全响应式:任何修改,组件自动响应更新。
- 轻量不依赖库:0 依赖,纯 React 实现。
- 适合中小项目:功能齐全但足够轻量。
🚀 延伸思考:还能干啥?
只要是全局可共享的状态,几乎都能套用这套组合:
- 登录用户信息管理
- 黑暗模式切换
- 多页签状态控制
- 表单多步骤流程状态
🧨 写在最后
如果你觉得 Redux 上手太重,MobX 学起来太玄,不妨从这套轻量方案起步。
它的思想是:
让状态管理可控、可预测、可共享
当你熟练掌握 useReducer + useContext,再借助自定义 Hook 进行封装 —— 你就真正跨过了 React 状态管理的门槛!
如果这篇文章有帮到你,不妨点个赞、留个言👇:
🗯️ “想看异步数据怎么配合这套玩?”
🗯️ “能不能支持持久化和懒加载状态?”
🗯️ “有没有结合 TypeScript 的版本?”
我都可以安排!
让我们一起写出更优雅、可维护、可拓展的 React 应用!