嗨,各位前端工友!👋 最近是不是又被React的状态管理搞得头大? useState写多了觉得散乱,用Redux又嫌太重?今天咱们就聊聊两个「轻量级但超实用」的API—— Reducer 和 Context ,看看它们是如何联手解决React开发中的「状态混乱」和「传参地狱」的。
先上结论: Reducer 管「怎么改」,Context 管「怎么传」 ,二者搭配使用,堪称中小型项目的状态管理黄金搭档!
什么是Context
Context 是 React 提供的跨组件数据共享方案,用于解决多层级组件间的 props 传递问题(prop drilling)。
核心作用 :
- 数据共享:允许父组件向其所有后代组件广播数据,无需手动逐层传递 props
- 简化状态传递:尤其适合主题设置、用户信息等全局状态
使用步骤 :
- 创建 Context:
const MyContext = createContext(defaultValue) - 提供数据:使用 <MyContext.Provider value={共享数据}> 包裹组件树
- 消费数据:通过
useContext(MyContext)在子组件中获取数据
什么是Reducer
Reducer 是一种状态管理模式,通过纯函数统一处理状态变更逻辑,配合 useReducer Hook 使用。
核心作用 :
- 集中管理复杂状态逻辑:将多个相关状态的修改逻辑整合到单一函数
- 可预测的状态变更:通过 action 类型明确状态修改意图,便于调试
- 处理复杂状态依赖:当多个状态相互关联或需要复杂计算时尤为适用
使用步骤 :
- 定义 reducer 函数:
(state, action) => newState - 初始化状态:
const [state, dispatch] = useReducer(reducer, initialState) - 触发状态更新:
dispatch({ type: 'ACTION_TYPE', payload: 数据 })
没有它们,我们会遇到什么麻烦?
假设我们要写一个TodoList:
- 组件A(AddTodo)需要添加任务 → 修改状态
- 组件B(TodoList)需要展示任务 → 读取状态
- 组件C(Footer)需要显示任务数量 → 读取状态 如果只用useState + props传递:
// 伪代码:props层层传递的噩梦
function App() {
const [todos, setTodos] = useState([])
return (
<div>
<AddTodo setTodos={setTodos} />
<TodoList todos={todos} setTodos={setTodos} />
<Footer todos={todos} />
</div>
)
}
当组件层级变深(比如TodoList里嵌套Item组件),props要传3层以上,改一处状态要动N个地方——这就是prop drilling( props钻取)。更要命的是,如果状态逻辑复杂(比如添加/删除/修改/筛选),useState的setter函数会散落在各个事件处理中,调试时根本找不到状态是在哪改的!
Reducer:让状态修改「有法可依」
Reducer的核心作用:把「状态修改逻辑」集中管理 ,就像给状态变化制定了「宪法」,所有修改必须按规则来。
先看下面的计数器实现:
// 1. 定义初始状态
const initialState = { count: 0, theme: 'light' }
// 2. 编写Reducer函数(状态修改的「宪法」)
function reducer(state, action) {
switch (action.type) {
case 'increment': // 增加计数
return { ...state, count: state.count + 1 }
case 'decrease': // 减少计数
return { ...state, count: state.count - 1 }
case 'changeTheme': // 切换主题
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' }
default: return state
}
}
// 3. 在组件中使用useReducer
function App() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
<h1>计数:{state.count}</h1>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'changeTheme' })}>切换主题</button>
</div>
)
}
Reducer解决了什么问题?
- 逻辑集中化 :所有状态修改逻辑都在
reducer函数里,想知道 count 怎么变的?搜reducer函数就行,不用满组件找setCount! - 可预测性 :修改状态必须通过
dispatch(action),action 的 type 字段(比如'increment')就是修改的「操作名」,调试时Redux DevTools能清晰显示每一步状态变化。 - 复杂状态管理 :当状态有多个子值(比如
{count, theme, user})或修改逻辑相互依赖时,reducer 比 useState 的多个 setter 函数优雅10倍!
Context:让状态传递「一步到位」
Reducer 解决了「怎么改」,但状态还得靠props传递?别急, Context的作用就是:跨组件共享状态,彻底消灭prop drilling !
看看下面这个项目是怎么做的。这是一个完整的 TodoList,用 Context 把状态「广播」给所有组件:
1. 创建Context(相当于「状态广播电台」)
import { createContext } from "react";
// 创建一个Context,默认值为null
export const TodoContext = createContext(null);
2. 用Provider「发射」状态(相当于「电台发射塔」)
import { TodoContext } from './TodoContext'
import { useTodos } from './hooks/useTodos'
import AddTodo from './components/AddTodo'
import TodoList from './components/TodoList'
function App() {
// 用useReducer封装状态和修改方法(这里useTodos内部调用了useReducer)
const todosHook = useTodos([]);
return (
{/* Provider包裹需要共享状态的组件树 */}
<TodoContext.Provider value={todosHook}>
<h1>Todo App</h1>
<AddTodo /> {/* 无需传props! */}
<TodoList /> {/* 无需传props! */}
</TodoContext.Provider>
)
}
3. 用useContext「接收」状态(相当于「收音机」)
import { useContext } from "react"
import { TodoContext } from "../TodoContext"
export function useTodoContext() {
// 直接从Context获取状态,无需通过props
return useContext(TodoContext)
}
现在,AddTodo 和 TodoList 组件可以直接通过 useTodoContext() 拿到todos和修改方法,比如AddTodo添加任务:
const { addTodo } = useTodoContext()
const handleSubmit = (e) =>{
e.preventDefault();
if(text.trim()){
addTodo(text.trim()) // 直接调用Context提供的方法
setText('')
}
}
以及 TodoList通过调用 useTodoContext hook取得数据并展示
import { useTodoContext } from '../hooks/useTodoContext'
...
const TodoList = ()=>{
const {
todos,
toggleTodo,
removeTodo
} = useTodoContext()
Context解决了什么问题?
- 跨组件传参 :无论组件嵌套多深,只要在Provider内部,就能直接获取状态,不用再写 props={props} 这种废话。
- 状态共享 :多个组件需要用同一个状态时(比如用户信息、主题设置),Context比全局变量更安全(React控制更新)。
Reducer + Context:强强联手的正确姿势
单独用Reducer只能管一个组件的状态,单独用Context虽然能传状态但状态修改逻辑还是散的。 把二者结合 ,才是中小项目的最优解!
经典搭配步骤:
- 用Reducer管理状态逻辑 :写一个 reducer 函数,定义所有状态修改规则(如 todoReducer)。
- 用useReducer创建状态 :在顶层组件(如App)中调用 useReducer,得到
[state, dispatch]。 - 用Context传递状态和dispatch :通过
Context.Provider把 state 和 dispatch 传给所有子组件。 - 子组件按需取用 :用
useContext获取 state(读)和 dispatch(改),实现「读写分离」。
看看这个项目最佳实践—— useTodos hook封装了reducer逻辑:
export function useTodos(initial = []){
const [todos, dispatch] = useReducer(todoReducer, initial)
// 把dispatch包装成具体方法(addTodo/toggleTodo),子组件无需知道dispatch细节
const addTodo= text =>{dispatch({type: 'ADD_TODO', text})}
const toggleTodo= id =>{dispatch({type: 'TOGGLE_TODO', id})}
return { todos, addTodo, toggleTodo }
}
这样一来,子组件拿到的是 addTodo 这种直观的方法,而不是底层的 dispatch ,代码可读性直接拉满!
什么时候该用它们?
| 场景 | 用useState | 用Reducer | 用Context | Reducer+Context |
|---|---|---|---|---|
| 简单状态(如开关、计数器) | ✅ 首选 | ❌ 小题大做 | ❌ 没必要 | ❌ 过度设计 |
| 复杂状态(多子值、多修改逻辑) | ❌ 散乱 | ✅ 首选 | ❌ 只管传不管改 | ❓ 单组件可用 |
| 跨组件共享简单状态 | ❌ prop drilling | ❌ 不解决传参 | ✅ 首选 | ❌ 没必要 |
| 跨组件共享复杂状态 | ❌ 灾难 | ❌ 只管改不管传 | ❌ 状态修改散乱 | ✅ 黄金搭档 |
总结:别再滥用Redux了!
很多同学一上来就用Redux,其实大部分中小项目根本用不上。 Reducer+Context已经能解决80%的状态管理问题 :
- Reducer :让状态修改「有迹可循」,适合复杂状态逻辑。
- Context :让状态传递「直达目标」,适合跨组件共享。
- 二者结合:既解决了「怎么改」,又解决了「怎么传」,代码干净又好维护!
Context像快递小哥,把状态包送到各个组件家门口;Reducer像交通指挥官,给状态变更制定红绿灯规则。俩兄弟联手,React状态管理从此告别「堵车」和「丢件」!