4.useReducer 相比useState优势体现在哪? 副本 来源:https://my.feishu.cn/docx/V4hbdf10mo8pUkxWg5Wc4q3hnkf 采集状态:已完成正文采集;已滚动到底部并按块及代码行号采集 useReducer vs. useState:在复杂场景中寻求更优的状态管理之道​ ​ 在 React 开发中,useState 是我们管理组件状态最常用的工具之一。它简单、直观,对于处理单个、独立的状态值(如开关状态、输入框内容)非常高效。​ ​ 但随着应用逻辑变得复杂,我们可能会遇到一些挑战:​ ​ • 一个组件内有多个 useState,它们之间相互关联,更新逻辑散落在各处。​ • 下一个状态的值严重依赖于前一个状态,逻辑变得难以追踪。​ • 需要将状态更新的逻辑传递给深层子组件,导致了不必要的函数重建和渲染。 在这些场景下,useReducer 提供了一种更结构化、更可预测的状态管理模式。它并非 useState 的替代品,而是在特定复杂度下更优的解决方案。​ ​ 从一个实例看差异​ ​ 我们来看一个常见的计数器场景。如果只是简单的加一,useState 绰绰有余。​ ​ const [count, setCount] = useState(0); // 在组件中直接调用 ​ 现在,我们为这个计数器增加一些复杂度:​ ​ 1. 点击增加/减少按钮,步长为 1。​ 2. 可以从输入框设置一个自定义步长。​ 3. 有一个重置按钮,将计数器恢复到初始值 0。​ 4. 计数值不能小于 0。 使用 useState,我们可能需要这样实现:​ ​ const [count, setCount] = useState(0); const [step, setStep] = useState(1); const handleIncrement = () => { setCount(c => c + step); }; const handleDecrement = () => { setCount(c => Math.max(0, c - step)); }; const handleReset = () => { setCount(0); }; // ... JSX 中绑定这些函数和 step 输入框 ​ 可以看到,状态更新的逻辑散落在三个不同的函数中,并且与组件本身紧密耦合。​ ​ 现在,我们用 useReducer 来重构它:​ ​ // 1. 在组件外部或顶部定义 Reducer 函数 const initialState = { count: 0, step: 1 }; function reducer(state, action) { switch (action.type) { case 'increment': return { ...state, count: state.count + state.step }; case 'decrement': return { ...state, count: Math.max(0, state.count - state.step) }; case 'set_step': return { ...state, step: action.payload }; case 'reset': return initialState; default: throw new Error(); } } // 2. 在组件中使用 useReducer const [state, dispatch] = useReducer(reducer, initialState); // 3. 在 JSX 中通过 dispatch 派发动作 dispatch({ type: 'set_step', payload: Number(e.target.value) })} /> ​ 通过这个对比,useReducer 的优势开始显现出来。​ ​ useReducer 的核心优势​ ​ 1. 状态逻辑与视图分离​ ​ 这是 useReducer 最核心的价值。​ ​ • useState: 状态更新的逻辑(setCount(count + 1))通常直接写在事件处理函数中,与组件的视图渲染代码混合在一起。​ • useReducer: 所有的更新逻辑都被抽离到 reducer 函数中。组件本身只负责“发出指令”(dispatch({ type: 'increment' })),而不关心状态具体“如何改变”。 这种分离带来了显而易见的好处:​ • 组件更纯粹:组件主要负责 UI 渲染和触发事件,变得更加简洁、易读。​ • 逻辑可复用:reducer 函数是一个纯函数,它可以被轻松地导出、导入,在多个组件间复用。​ • 更易于测试:你可以独立于 React 组件,对 reducer 函数进行单元测试,只需验证输入特定的 state 和 action 是否能得到预期的 newState。 2. 更可预测的状态流与更便捷的调试​ ​ 当状态逻辑复杂时,追踪 useState 引起的变化会很困难。而 useReducer 遵循了类似 Redux 的模式,让状态变更的路径清晰可见。​ ​ 每一次状态更新都必须通过 dispatch一个action来触发。action通常是一个包含 type 属性的对象,清晰地描述了“发生了什么”。我们可以轻易地在 reducer 函数或中间件中打印 action 日志,从而得到一个非常清晰的状态变更轨迹,极大地简化了调试过程。​ ​ console.log(action) 就像是为你的状态变更安装了一个监控摄像头。​ ​ 3. 优化深层组件的性能​ ​ 在复杂的应用中,我们经常需要将状态更新的能力传递给多层嵌套的子组件。​ ​ • 使用 useState: 如果我们向下传递 setCount 函数,或者一个包裹了 setCount 的新函数(如 () => setCount(count + 1)),这个函数在父组件每次重新渲染时,其引用地址都可能发生变化。如果子组件使用了 React.memo 进行优化,这种变化会破坏 memo 的效果,导致不必要的重渲染。 • 使用 useReducer: useReducer 返回的 dispatch 函数在组件的整个生命周期内是**引用稳定**的。这意味着你可以放心地将 dispatch 传递给深层子组件。即使父组件重新渲染,dispatch 的引用也不会改变,从而避免了对 React.memo 包裹的子组件产生不必要的渲染。 我们应该如何选择?​ ​ 理解了各自的优势后,选择的依据就变得清晰了。​ ​ 场景 推荐使用 原因 状态是独立的、简单的值(布尔、数字、字符串) useState 写法最简洁,心智负担最小。 状态是一个对象或数组,但更新逻辑非常简单 useState 依然够用,例如 setForm({ ...form, name: 'new' })。 多个状态值相互关联、联动更新 useReducer 所有更新逻辑收敛在一个 reducer 中,避免逻辑遗漏和不一致。 下一个状态依赖于复杂的计算或前一个状态 useReducer reducer(state, action) 的模式天然适合这种场景,逻辑清晰。 希望将状态逻辑与组件分离,便于测试或复用 useReducer reducer 是纯函数,易于独立测试和复用。 需要将状态更新方法深层传递给子组件 useReducer 稳定的 dispatch 函数可以有效配合 React.memo 进行性能优化。 构建一个小型状态机 useReducer action.type 可以清晰地代表状态机的“事件”,reducer 负责状态转移。 ​ 总结​ ​ useReducer 并非 useState 的替代,而是 React 工具箱中一把用于处理复杂状态的“瑞士军刀”。当 useState 让你的组件逻辑开始变得混乱时,往往就是引入 useReducer 的最佳时机。​ ​ 它通过分离逻辑与视图、提供可预测的状态流以及优化深层传递,帮助我们构建出更健壮、更易于维护的 React 应用。希望通过本文的梳理,我们能够更自信地在 useState 和 useReducer 之间做出最适合当前场景的选择。​ ​ 本文暂未被其它文档引用