React Strict Mode下setTimeout触发两次的原因排查
React Strict Mode下setTimeout重复触发的问题
组件代码
function ExportViewerPage(): JSX.Element { const [hasPrinted, setHasPrinted] = useState(false); if (!hasPrinted) { setHasPrinted(true); console.log("Inside if loop, hasPrinted: ", hasPrinted) setTimeout(() => { console.log("Executing setTimeout: ", hasPrinted) }); } return (<div>Page</div>) } export default ExportViewerPage;
现象描述
预期setTimeout里的代码只执行一次,但实际控制台输出两次:
Inside if loop, hasPrinted: false Executing setTimeout: false Executing setTimeout: false
奇怪的是setTimeout上方的日志只输出一次,说明定时器只被调用了一次,但回调却执行了两次。关闭Next.js配置文件next.config.js中的reactStrictMode后,问题消失,定时器仅触发一次。
原因解析
这是React Strict Mode在开发环境下的故意设计,目的是帮开发者排查潜在的代码问题:
- Strict Mode会重复执行组件渲染函数、状态更新相关逻辑,以此检测非纯函数操作(比如在渲染里直接改状态、创建副作用)。
- 你的代码直接在渲染流程里调用
setHasPrinted(true)和创建定时器,违反了React“渲染函数必须是纯函数”的规则。第一次渲染时创建了定时器,Strict Mode触发第二次渲染时,虽然hasPrinted已经变为true不会再进入if分支,但第一次创建的定时器没有被清理,加上Strict Mode对异步操作的检测机制,最终导致回调被执行两次。 - 关闭Strict Mode后,React不再执行重复渲染流程,所以问题不会出现。
解决方案
把副作用逻辑放到useEffect中,并添加清理函数,确保副作用能被正确处理:
import { useState, useEffect } from 'react'; function ExportViewerPage(): JSX.Element { const [hasPrinted, setHasPrinted] = useState(false); useEffect(() => { if (!hasPrinted) { setHasPrinted(true); console.log("Inside useEffect, hasPrinted: ", hasPrinted); const timer = setTimeout(() => { console.log("Executing setTimeout: ", hasPrinted); }); // 组件卸载或依赖变化时清理定时器 return () => clearTimeout(timer); } }, [hasPrinted]); return (<div>Page</div>) } export default ExportViewerPage;
这样处理后,即使在Strict Mode下,useEffect的清理函数会先清除第一次创建的定时器,第二次执行useEffect时hasPrinted已经是true,不会再创建新的定时器,最终只会触发一次回调。
内容的提问来源于stack exchange,提问作者DanBoghean
相关产品推荐
相关产品推荐

