React useState初始化函数无执行痕迹导致counter意外自增问题求助
问题原因分析
核心原因
该现象是 React 18+ 开发环境严格模式(StrictMode) 的特性导致的,和闭包、Hook 本身的逻辑无关。
具体运行流程
React 18 开启严格模式后,开发环境下会对所有组件执行「首次挂载→立即卸载→二次挂载」的逻辑,目的是提前暴露组件清理逻辑、副作用不符合规范的问题,该行为只会在开发环境出现,生产环境不会触发。
你的代码运行的完整流程如下:
- 初始全局
counter值为 0 - 第一次挂载
Child组件:- 执行
useState的惰性初始化回调,counter自增为 1,打印标记(1)的日志 - 执行标记(2)的日志,输出id为1
- 该次挂载的组件会被立即卸载,相关实例被丢弃
- 执行
- 第二次挂载
Child组件:- 这是最终保留在页面上的组件实例,
useState会重新执行初始化回调,counter自增为 2 - 标记(1)(2)的日志正常触发,但 React 会默认静默严格模式下二次挂载的所有
console.log输出,所以控制台看不到这部分日志 - 最终组件拿到的
id是 2,按钮点击回调的闭包捕获的就是这个值,全局counter此时也为 2
所以点击按钮时会输出id: 2 counter: 2,本质是实际展示的组件是第二次挂载的实例,而第一次挂载的日志没有被静默,形成了初始化逻辑只执行了一次的错觉。
- 这是最终保留在页面上的组件实例,
验证方法
- 移除项目外层的
<StrictMode>标签后重新运行,就会得到预期的id:1 counter:1的点击输出 - 在
useState的初始化回调中加入debugger断点,可以明确看到回调执行了两次
方案优化建议
要实现「为组件调用位点生成唯一ID」的需求,React 18 已经提供了原生的useId Hook,不需要自己维护全局计数器,可以直接替换实现,避免全局可变变量带来的各类问题。
内容的提问来源于stack exchange,提问作者Nearoo
相关产品推荐
相关产品推荐

