该场景下React useRef与组件外全局let变量的使用差异是什么
两种定时器ID存储方案的差异与最优选择
核心差异
两种方案虽然单组件实例下运行表现一致,但底层逻辑本质不同:
- 作用域与共享逻辑不同
Case1中组件外声明的let cursorTimer是模块级全局变量,只要当前JS模块被加载,所有渲染的Item组件实例会共享同一个变量。如果页面同时存在多个Item实例,不同组件的定时器逻辑会互相干扰,出现清掉其他组件定时器、状态错乱的问题。
Case2的useRef是绑定到React组件实例的存储对象,你可以根据业务需求灵活选择:要多个组件共享定时器就把ref声明在公共父组件,要每个组件独立定时器就直接在Item内部声明useRef,不会出现跨实例的状态污染。 - 生命周期绑定能力不同
模块级变量不会随组件卸载自动重置,组件销毁后如果定时器未触发,残留的ID会一直留在内存中,下次组件重新挂载时会直接读到上一次的残留值,容易触发内存泄漏、异常定时器执行等问题。useRef的生命周期和对应组件完全绑定,组件卸载时ref对象会同步被回收,配合useEffect的清理函数可以很方便地在组件卸载时统一清除残留定时器,不会遗留垃圾状态。 - 场景兼容性不同
模块级变量完全不兼容SSR(服务端渲染)场景:SSR环境下模块变量是服务端进程级共享的,多个用户的请求会读写同一个变量,直接出现跨用户的状态污染。同时在单元测试场景下,模块变量不会随单测用例执行自动重置,需要额外写清理逻辑,否则不同用例之间会互相干扰,测试稳定性极差。useRef是React渲染流程内的对象,SSR场景下每次渲染组件都会生成独立的ref,不会出现跨请求污染;单测场景下每个测试用例渲染的组件都是独立的,不需要额外处理变量重置逻辑。
最优选择
性能上两种方案没有明显差异,都是读写基础类型值,开销可以忽略不计。但实际开发中优先选择useRef方案,鲁棒性远高于模块级变量方案,避免后续迭代加功能、换架构时踩隐性坑。
内容的提问来源于stack exchange,提问作者Evan Jin
相关产品推荐
相关产品推荐

