在React的世界里,状态管理如同乐团的指挥棒,决定着应用的和谐与流畅。今天,我们将深入探索
useReducer和useContext这对黄金搭档,看看它们如何携手打造优雅的全局状态管理方案。本文是继 📢 React状态管理新姿势:useReducer与纯函数的完美协奏曲 写得,感兴趣的可以看看这篇文章,更加能够理解useReducer的用法
今天我们要实现的具体效果,是我们老生常谈的todoList功能,涉及自定义Hook、useReducer、useContext的组合使用,废话不多说,我们先来看效果:
第一乐章:useReducer - 状态管理的精密机器
想象你有一个复杂的国家需要治理(即应用状态),直接修改国家属性容易引发混乱。useReducer就像建立一套完善的法律体系,通过预定义的规则管理状态变化。
export default function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, {
id: Date.now(),
text: action.text,
done: false
}]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
)
case 'REMOVE_TODO':
return state.filter(todo => todo.id !== action.id)
default:
return state
}
}
这个reducer函数是状态管理的核心宪法:
- ADD_TODO:添加新待办事项,使用时间戳作为唯一ID
- TOGGLE_TODO:切换完成状态,巧妙使用map避免直接修改原数组
- REMOVE_TODO:过滤删除指定事项,保持数组纯净
通常在开发中,我们把reducer函数,都放在reducers目录下,方便管理
在自定义hook中,我们封装了reducer的使用:
export function useTodos(initial = []) {
const [todos, dispatch] = useReducer(todoReducer, initial)
const addTodo = text => dispatch({ type: 'ADD_TODO', text })
const toggleTodo = id => dispatch({ type: 'TOGGLE_TODO', id })
const removeTodo = id => dispatch({ type: 'REMOVE_TODO', id })
return { todos, addTodo, toggleTodo, removeTodo }
}
比喻时刻:把useReducer想象成一家高级寿司店的操作手册。主厨(reducer)只接受特定格式的订单(action对象),如{type: '切鱼', amount: 3}。学徒(组件)不能随意操作食材(状态),必须通过传票(dispatch)下单。这样确保每份寿司(状态变更)都符合标准流程。
同样的自定义Hook也是,放在统一目录下方便管理,这里我们封装了两个自定义Hook,一个是useTodoContext(用来封装 createContext和useContext的使用方便我们的复用) 一个是useTodos(也就是封装reducer的使用):
第二乐章:useContext - 跨层级通信的秘密通道
当应用组件树越来越深,props逐层传递就像用竹篮打水——费力且低效。useContext就是为此而生的通信隧道。
首先建立上下文:
// TodoContext.js
export const TodoContext = createContext(null)
然后在顶层提供数据:
// App.jsx
function App() {
const todosHook = useTodos() //封装好的reducer和状态
return (
<TodoContext.Provider value={todosHook}>
<h1>Todo App</h1>
<AddTodo />
<TodoList />
</TodoContext.Provider>
)
}
最后在任何子组件中获取:
这里我们是封装好的useTodoContext
// hooks/useTodoContext.js
export function useTodoContext() {
return useContext(TodoContext)
}
比喻时刻:想象useContext是魔法世界的猫头鹰邮政系统。霍格沃茨(App组件)有个中央邮局(Provider),任何学生(子组件)只需说一句"Accio context!"(useContext),就能直接收到包裹(状态),无需经过级长(中间组件)转手。
第三乐章:双剑合璧 - 全局状态管理的交响曲
当useReducer和useContext结合,便奏响了状态管理的完美和弦,在组件中使用如行云流水:
// components/AddTodo/index.jsx
export default function AddTodo() {
const [text, setText] = useState('')
const { addTodo } = useTodoContext()
const handleSubmit = (e) => {
e.preventDefault()
if (text.trim()) {
addTodo(text.trim()) // 直接调用全局方法
setText('')
}
}
return (
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button type="submit">Add</button>
</form>
)
}
列表组件同样优雅:
// components/TodoList/index.jsx
export default function TodoList() {
const { todos, removeTodo, toggleTodo } = useTodoContext()
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => removeTodo(todo.id)}>remove</button>
</li>
))}
</ul>
)
}
比喻时刻:这就像城市供电系统。发电厂(useReducer)生产电力(状态),电网(Context)将电力输送到千家万户(组件)。当你按下开关(触发action),电流(状态变更)立即点亮整个城市的灯泡(UI更新),无需自己购买发电机(本地状态)。
第四乐章:自定义Hook - 状态逻辑的封装艺术
自定义Hook是React的瑞士军刀,让状态逻辑可复用且解耦:
// hooks/useTodos.js
export function useTodos(initial = []) {
const [todos, dispatch] = useReducer(todoReducer, initial)
// 封装操作函数
const addTodo = text => dispatch({ type: 'ADD_TODO', text })
const toggleTodo = id => dispatch({ type: 'TOGGLE_TODO', id })
const removeTodo = id => dispatch({ type: 'REMOVE_TODO', id })
return { todos, addTodo, toggleTodo, removeTodo }
}
这带来了三大优势:
- 关注点分离:组件专注UI渲染,逻辑由hook处理
- DRY原则:避免在多个组件中重复相同的状态逻辑
- 可测试性:纯状态逻辑可以独立于组件进行测试
比喻时刻:把自定义Hook看作智能手机的操作系统。组件是手机APP(微信、相机),hook是底层系统服务(GPS、蓝牙)。APP不需要知道定位如何实现,只需调用"获取位置"接口。换手机(组件)时,系统服务(hook)保持稳定不变。
终章:模式优势与适用场景
模式优势对比表:
| 特性 | useState本地状态 | Redux全局状态 | useReducer+Context |
|---|---|---|---|
| 学习曲线 | ★☆☆☆☆ (简单) | ★★★★☆ (复杂) | ★★☆☆☆ (中等) |
| 代码量 | 少 | 多 | 中等 |
| 跨组件通信 | 困难 | 优秀 | 优秀 |
| 中间件支持 | 无 | 丰富 | 需手动实现 |
| 适用场景 | 简单组件 | 大型应用 | 中小型应用 |
落幕:思考与展望
通过今天的探索,我们见证了useReducer和useContext如何优雅地解决React状态管理难题。它们像阴阳两极——useReducer提供结构化的状态更新,useContext提供高效的跨组件通信。
在项目实践中,我发现这种模式特别适合:
- 当props drilling超过三层组件时
- 状态更新逻辑复杂涉及多个子值时
- 需要重用相同状态逻辑的多个组件
随着React生态发展,虽然出现了像Recoil、Jotai等新锐状态管理库,但useReducer+Context作为官方解决方案,凭借其零依赖、概念简洁、与React深度集成的优势,依然是中小型应用的最佳选择。