
react 18 的 strictmode 会故意双倍调用组件渲染和副作用函数(如事件处理器),导致 alert()、confirm() 等同步 ui 对话框意外弹出两次;通过 settimeout(fn, 0) 延迟执行可绕过该行为,确保仅触发一次。
react 18 的 strictmode 会故意双倍调用组件渲染和副作用函数(如事件处理器),导致 alert()、confirm() 等同步 ui 对话框意外弹出两次;通过 settimeout(fn, 0) 延迟执行可绕过该行为,确保仅触发一次。
在 React 应用中,尤其是使用 React 18 及其默认启用的 <StrictMode> 时,开发环境下组件的初始化逻辑(包括事件处理函数)会被有意地执行两次——这是 StrictMode 的调试机制,用于提前暴露不纯的副作用(如直接修改 DOM、发起网络请求、调用 alert/confirm 等)。虽然这有助于构建更健壮的组件,但它会让依赖同步浏览器 API(如 window.alert 或 window.confirm)的逻辑出现重复弹窗问题。
你当前的 handleSolved 和 handlePlayAgain 函数正是如此:handleSolved 触发 alert("Good job!") 后立即调用 handlePlayAgain(),而后者又在条件分支中再次调用 confirm() 或 alert()。由于 StrictMode 导致 handleSolved 被调用两次,整个对话流便重复执行,造成“弹窗两次”的现象。
✅ 正确解决方案是将有副作用的 UI 操作移至微任务队列中执行,即使用 setTimeout(..., 0)(或 Promise.resolve().then(...)),使其脱离 StrictMode 的双重调用上下文:
const handlePlayAgain = () => {
const confirmPlayAgain = window.confirm("Do you want to play another puzzle?");
if (confirmPlayAgain) {
// 使用 setTimeout 延迟状态更新,避免 StrictMode 双重触发副作用
setTimeout(() => {
const newIndex = getRandomIndex();
setImageIndex(newIndex);
}, 0);
} else {
alert("Thank you for playing");
}
};
const handleSolved = () => {
alert("Good job!");
handlePlayAgain(); // 此处调用已受 setTimeout 保护
};⚠️ 注意事项:
- 不要禁用 <StrictMode>:虽然移除它能“暂时”消除双弹窗,但会掩盖潜在的副作用隐患(如内存泄漏、竞态条件),且可能引发 ResizeObserver loop completed with undelivered notifications 等警告——这通常表明布局测量与更新未正确同步,恰恰是 StrictMode 帮你发现的问题。
- setTimeout(fn, 0) 并非“黑魔法”,它将逻辑推入下一个宏任务,确保在 StrictMode 的初始双渲染完成后执行,从而只响应用户真实交互。
- 若需更高一致性,也可改用 useEffect + ref 标记防重,但对简单对话框场景,setTimeout 是最轻量、语义清晰的解法。
总结:StrictMode 是开发利器而非障碍。面对 alert/confirm 重复问题,应主动适配其设计哲学——将副作用延迟到渲染提交后执行,而非规避它。这样既保持了开发环境的严格性,又保障了用户交互的准确性。


















