为什么React StrictMode下全局变量控制的定时器无法更新state?
问题原因分析
这个现象是React 18+开发环境下严格模式的专门设计和你代码里的副作用写法不规范共同导致的,完整执行逻辑如下:
- React严格模式在开发环境会对组件执行两次挂载流程:第一次挂载完成后会立刻模拟卸载该组件实例,再重新挂载一个新的组件实例,目的是提前暴露开发者没有清理副作用导致的bug。
- 你用全局变量
firstRender控制定时器只注册一次,而全局变量的状态不会随组件实例的销毁重置,就出现了如下执行流程:- 初始状态全局
firstRender = true,第一次渲染旧组件实例,满足条件注册定时器,随后firstRender被置为false,旧实例挂载完成 - 严格模式触发模拟卸载,旧实例被标记为已卸载,已经注册的定时器不会被自动清理
- 严格模式触发第二次挂载,渲染新的组件实例,此时全局
firstRender已经是false,不会再注册新的定时器 - 1秒后定时器触发,调用的是已经被卸载的旧实例的
setData,React会直接忽略已卸载组件的状态更新,新实例的状态完全不受影响,因此页面始终显示Nick
- 初始状态全局
为什么换成useRef就不会复现?
useRef是绑定到单个组件实例的状态,严格模式下第二次挂载新实例时会重新初始化useRef的值为true,因此新实例会正常注册定时器,定时器触发时调用的是当前挂载的新实例的setData,状态可以正常更新。
正确写法
永远不要把副作用(定时器、事件监听、接口请求等)直接写在组件渲染逻辑中,应该放到useEffect中并配套清理逻辑:
import { useState, useEffect } from 'react'; export default function App() { let [data, setData] = useState({ name: 'Nick' }); useEffect(() => { const timer = setTimeout(() => { setData((ps) => ({ ...ps, name: 'Paul', })); }, 1000); // 组件卸载时清理定时器,避免副作用残留 return () => clearTimeout(timer); }, []); // 空依赖保证只在挂载时执行一次 return ( <div> <h1>{data.name}</h1> <p>Start editing to see some magic happen :)</p> </div> ); }
这种写法下不管是否开启严格模式都能正常运行:第一次挂载的定时器会在模拟卸载时被清理,第二次挂载会重新注册新的定时器,状态更新正常。
内容的提问来源于stack exchange,提问作者gmoniava
相关产品推荐
相关产品推荐

