React 18 StrictMode下useEffect状态异常问题咨询
这是React 18 StrictMode的预期行为,并非Bug。StrictMode仅在开发环境生效,通过模拟"挂载→卸载→重新挂载"的流程,检测可能导致内存泄漏的不规范副作用。
现象原因拆解
useId的特性:
StrictMode下第一次渲染生成的ID会被直接丢弃,第二次渲染生成的ID会被保留并用于后续真实渲染。这是因为useId的设计目标是生成组件生命周期内稳定的唯一ID,重复渲染模拟了组件卸载再挂载的场景,确保最终使用的ID符合稳定要求。useRef的初始化逻辑:
每次渲染(包括StrictMode的两次渲染)都会重新执行useRef的初始化,将ref.current设为初始值9。第一次渲染时你修改了ref.current为随机数A,但模拟卸载后,第二次渲染组件重新初始化,ref.current再次被设为9,随后修改为随机数B。Effect的捕获与执行逻辑:
StrictMode下,React会先执行第一次渲染的Effect,随即调用其清理函数(模拟卸载),然后执行第二次渲染的Effect。你日志中未出现第一次渲染的Effect日志,是因为React在开发环境的双重渲染机制中,会丢弃第一次渲染的状态关联,最终仅保留第二次渲染的Effect执行结果。
解决方案
要适配StrictMode的行为,核心是确保副作用的幂等性,并保证清理函数能正确撤销副作用:
禁止在渲染阶段修改ref或执行副作用:
渲染阶段必须是纯函数,所有副作用(如修改ref、DOM操作、数据请求等)都应放在useEffect或useLayoutEffect中:const ref = useRef(9); const id = useId(); console.log('@@ initial id', id); console.log('@@ initial ref', ref.current); useEffect(() => { // 将修改ref的逻辑移至Effect内 ref.current = Math.random(); console.log('@@ random ref', ref.current); console.log('@@ effect id', id); console.log('@@ effect ref', ref.current); return () => { // 清理时恢复初始值或执行对应撤销逻辑 ref.current = 9; console.log('@@ unmount id', id); console.log('@@ unmount ref', ref.current); }; }, []); // 空依赖确保仅挂载时执行一次确保清理函数能完全撤销副作用:
若副作用涉及订阅、定时器、事件监听等,必须在清理函数中完整撤销,避免两次渲染导致的资源泄漏:useEffect(() => { const subscription = someAPI.subscribe(data => { /* 处理逻辑 */ }); const timer = setInterval(() => { /* 定时逻辑 */ }, 1000); return () => { subscription.unsubscribe(); clearInterval(timer); }; }, []);正确设置Effect依赖项:
确保useEffect的依赖项完整覆盖所有用到的状态和props,让React能正确跟踪变化,避免不必要的重复执行,同时保证Effect能捕获当前渲染周期的正确状态。接受开发环境的双重渲染行为:
StrictMode仅在开发环境生效,生产环境不会触发两次渲染,因此无需为生产环境修改核心逻辑,只需保证开发环境下的双重渲染不会引发异常即可。
内容的提问来源于stack exchange,提问作者fragsalat

