使用React Hooks实现依赖其他状态的状态更新的最优方案
两种现有方案对比
方案1(在setState1回调中更新state2)
优先选择这个方案,它的优势很明确:
- 全程只触发1次组件重渲染,性能更好
- 逻辑完全收敛在
changeState1方法里,只有主动调用这个方法修改state1的时候才会同步更新state2,不会出现意外的副作用,比如其他业务场景修改state1的时候误触发state2更新
唯一的缺点是如果你的项目里有很多地方都会修改state1,每处修改的位置都要手动同步state2的更新逻辑,容易漏写,维护成本会变高。
方案2(用useEffect监听state1更新)
这个方案是不推荐的,问题比较多:
- 会触发2次不必要的重渲染:第一次是state1更新后组件重渲染,然后useEffect执行触发state2更新,导致组件第二次重渲染
- 逻辑分散,后续如果有人在其他地方修改了state1,不管是不是想要同步state2,都会自动触发更新,很容易出现预期外的bug
- useEffect本身的依赖管理也容易出问题,后续改逻辑很容易漏改依赖导致bug,排查成本高
更优的解决方案
优先看你能不能简化状态,从根源上消除状态同步的需求:
优先选择:派生状态
如果你的state2不需要独立修改,完全可以基于state1(以及历史的state1值)计算出来,根本不需要单独声明为useState状态:
import { useState, useMemo } from 'react'; const DependentStateExample = () => { const [state1, setState1] = useState(0); // 假设你要的逻辑是累加每次state1变更的增量,这里可以直接计算 const [state1History, setState1History] = useState([0]); const state2 = useMemo(() => state1History.reduce((sum, val) => sum + val, 0), [state1History]); const changeState1 = () => { setState1(prev => { const newState = prev + 5; setState1History(prevHistory => [...prevHistory, newState]); return newState; }); }; return ( <div> <span>{state1}</span><span>{state2}</span> <button onClick={changeState1}>Change State 1</button> </div> ); };
这种方案完全没有状态同步的问题,也没有多余的重渲染,逻辑最简洁,只要业务场景符合优先选这个。
次选:合并关联状态
如果state1和state2确实是强关联的,每次更新state1都要同步更新state2,可以把两个状态合并成一个对象,一次更新搞定:
import { useState } from 'react'; const DependentStateExample = () => { const [state, setState] = useState({ state1: 0, state2: 0 }); const changeState1 = () => { setState(prev => { const newState1 = prev.state1 + 5; return { ...prev, state1: newState1, state2: prev.state2 + newState1 }; }); }; return ( <div> <span>{state.state1}</span><span>{state.state2}</span> <button onClick={changeState1}>Change State 1</button> </div> ); };
这种方案同样只有一次重渲染,逻辑集中,不需要维护额外的监听,也不会漏更新。
最后选择:useReducer
如果修改state1的入口非常多,每个地方都写同步逻辑太麻烦,可以用useReducer把更新逻辑收敛在reducer函数里,统一处理:
import { useReducer } from 'react'; const reducer = (state, action) => { switch (action.type) { case 'INCREMENT_STATE1': const newState1 = state.state1 + 5; return { ...state, state1: newState1, state2: state.state2 + newState1 }; default: return state; } }; const DependentStateExample = () => { const [state, dispatch] = useReducer(reducer, { state1: 0, state2: 0 }); const changeState1 = () => dispatch({ type: 'INCREMENT_STATE1' }); return ( <div> <span>{state.state1}</span><span>{state.state2}</span> <button onClick={changeState1}>Change State 1</button> </div> ); };
选择优先级总结
- 能把state2做成派生状态,就不要单独声明为useState状态
- 不能派生的话,优先合并关联状态或者用useReducer集中处理更新逻辑
- 其次选你写的示例1的方案
- 只有当所有前面的方案都不适用的时候,再考虑用示例2的useEffect方案
内容的提问来源于stack exchange,提问作者Panox
相关产品推荐
相关产品推荐

