React中将值同时存于state和ref是常用实践吗?哪种实现更优?
React定时器访问最新state方案对比
模式通用性说明
把state同步到ref是React官方认可的常用模式,专门用于解决定时器、异步回调、全局事件监听等场景下,闭包捕获旧state无法拿到最新值的问题,完全符合React的设计逻辑,没有原理性问题。
三种方案优缺点对比
- 方案1:渲染时直接给ref赋值
这是目前性价比最高的写法,代码简洁,同步赋值没有额外性能开销。唯一的小限制是部分严格的lint规则可能会报渲染过程中修改ref的警告,但这个规则本身是可配置的,实际运行没有任何隐患,绝大多数场景下都可以正常使用。 - 方案2:useEffect监听state变化更新ref
这个写法严格贴合React「副作用放在useEffect中执行」的心智模型,不会触发任何lint规则报错。性能略低于方案1,但差距极小,只有在state高频更新(每秒上万次)的场景下才会有感知。如果你的团队对代码规范要求极高,或者是开发开源组件需要兼容所有规范要求,可以选择这个方案。 - 方案3:自定义setter同步更新state和ref
这个方案可维护性最差,存在同步失效的隐患:只要有任何一处修改state没有走你封装的自定义setter(比如直接调用原生setNumberState,或者使用函数式更新),就会出现ref和state值不一致的问题,除非你能完全收口所有state修改的入口,否则不推荐使用。
业界更青睐的实现
现在业界主流优先选择方案1,很多项目甚至会把这个逻辑封装成通用的useLatest工具hook,代码如下:
// 通用hook封装 function useLatest(value) { const ref = useRef(value); ref.current = value; return ref; } // 组件内使用 const numberRef = useLatest(number);
这个hook已经成为React生态里的通用工具hook,几乎所有React工具库都有内置实现。
其他替代方案
如果不想同时维护state和ref,还有两种实现方式:
- 把state加入定时器所在useEffect的依赖数组,每次state变化时销毁旧定时器重建新定时器:
useEffect(() => { const timer = setInterval(() => { if (number % 2 === 0) console.log('Yay!'); }, 1000); return () => clearInterval(timer); }, [number]);
这个方案的缺点是每次state更新都会重置定时器的计时,适合state更新频率低、对定时器触发时间精度要求不高的场景。
- 把定时器回调的逻辑改成从setState的函数式参数里拿最新值,不过这个方案只适合需要修改state的场景,你的需求是读state打日志,不太适用。
内容的提问来源于stack exchange,提问作者Robo Robok
相关产品推荐
相关产品推荐

