React中何时用全局变量替代State或useMemo?全局存非响应数据是否为反模式?
关于模块作用域存储非响应跨渲染数据的问题解答
这种写法属于反模式吗?
你提到的var cityList本质是JS模块作用域下的模块级变量,并非绑定在单个组件实例上的存储。如果把组件自身需要的跨渲染数据直接存在这种模块级变量里,在绝大多数日常开发场景下都属于反模式,哪怕代码里没有任何对该数据变更的响应逻辑。
原示例代码本身存在几处语法问题:
useEffects拼写错误,React对应的副作用Hook为useEffect- 条件判断行多打了一个右括号:
if (!cityList)}{会直接触发JS语法解析错误
这种写法存在的核心问题
- 组件实例隔离性完全丢失:只要所在JS模块不被销毁,所有渲染出的
Component实例都会共享同一个cityList值。如果后续出现不同组件实例需要加载差异化数据的需求(比如不同区域的组件拉取对应城市列表),会直接出现数据互相覆盖、逻辑错乱的bug。 - 重复请求与无效请求风险:你在
useEffect中通过!cityList判断是否发请求的逻辑,没有任何请求去重机制:短时间内多个组件同时挂载时,会同时发起完全重复的API请求;如果组件刚挂载就被卸载(比如路由快速跳转、条件渲染立即移除组件),异步加载流程不会被取消,最终会把无人需要的数据赋值给模块变量,造成不必要的资源消耗。 - 与React渲染机制脱节的隐性bug:你默认该数据是“非响应被动数据”,但只要组件渲染逻辑中直接读取了
cityList,就会出现逻辑断层:第一次渲染时cityList为undefined,会渲染出空的下拉选项;等异步请求完成给cityList赋值时,因为没有触发组件重渲染,用户永远看不到加载完成的有效数据。这类问题没有任何代码报错,排查成本极高。 - 开发与测试的污染问题:开发环境下的模块热更新机制,不会主动清空模块作用域内的变量值,会出现改了请求逻辑、但页面还是显示旧缓存数据的灵异问题;单元测试场景下,不同测试用例之间会共享这个模块变量,测试结果会互相污染,没法独立断言组件逻辑。
对比State、useMemo,这类模块级变量的合理使用场景
首先要明确三类存储的核心差异,才能划清使用边界:
useState:值变更会主动触发组件重渲染,专为需要响应式更新的渲染数据设计useMemo/useRef:存储的值绑定在单个组件实例上,跨渲染留存,组件卸载后就会被回收,不会在多个实例之间共享- 模块级变量:存储的值绑定在模块作用域上,跨组件实例、跨渲染共享,组件卸载后也不会被回收,值变更不会触发任何组件重渲染
对应到合理的使用场景,只有满足以下条件时,用模块级变量存储才没有问题:
- 存储的是真·跨所有实例共享的通用缓存:比如确认城市列表是全局通用、和组件上下文、用户权限完全无关的公共数据,需要做“全应用只请求一次、所有模块都能复用”的简单缓存。这种场景也要补充基础的健壮性逻辑,比如把正在进行的请求Promise也存在模块变量里做请求去重,增加手动失效的能力,不能只存接口返回值。
- 存储的是不参与渲染逻辑的纯工具实例:比如模块级初始化的长连接Socket实例、埋点SDK实例、第三方图表渲染引擎实例。这类对象本身不需要在页面上展示,值变更也不需要触发组件重渲染,只是组件调用的公共工具,完全没必要每个组件实例都重复创建,存在模块级是完全合理的。
- 存储的是加载时就固定的常量配置:比如全局枚举值、静态路由表这类在模块加载时就确定、运行时不会修改的固定值,本质和写
const CONFIG = {}的静态常量没有区别,不存在问题。
你提到的“没有逻辑对数据变更做出响应,所以用State没有意义”的判断本身没有错,但这种单组件需要的、非响应式的跨渲染数据,更适合存在useRef里——既能实现跨渲染留存、不触发不必要的重渲染,又能保证实例隔离,从根源上避免模块级变量带来的共享污染问题。
内容的提问来源于stack exchange,提问作者Abraham
相关产品推荐
相关产品推荐

