在使用 React 18 的并发渲染(Concurrent Rendering)特性时,为什么 useEffect 中的副作用有时会执行多次?

在使用 React 18 的并发渲染(Concurrent Rendering)特性时,为什么 useEffect 中的副作用有时会执行多次?

回答与解析:
在 React 18 启用并发渲染(例如使用 createRoot 而非 legacy ReactDOM.render)后,React 可能在某些情况下对组件进行“可中断的渲染”或“重试渲染”。这意味着 React 可能在真正将 UI 提交到 DOM 之前,多次调用组件函数(包括其内部的 useEffect)以探索更优的渲染路径。虽然这些“预备性”的渲染不会实际挂载到 DOM,但在开发模式下(尤其是 Strict Mode),React 会故意双调用 useEffect(及其清理函数)来帮助开发者发现潜在的副作用问题。

具体来说:

  1. 开发模式 + Strict Mode:React 会故意 mount → unmount → mount 组件一次,导致 useEffect 执行两次(及其清理函数)。这是为了暴露非幂等或依赖时序的副作用。
  2. 并发渲染本身:在极少数情况下(如高优先级更新打断低优先级更新),React 可能放弃一次渲染,导致副作用被安排但随后取消。虽然这不会“执行多次”副作用,但可能造成副作用执行时机的不确定性。

如何应对?

  • 确保 useEffect 中的副作用是幂等可清理的。
  • 总是提供 useEffect 的清理函数,释放资源(如订阅、定时器)。
  • 避免在 useEffect 中执行不可逆的操作(如直接修改外部变量而不清理)。
  • 使用生产构建(production build)时,Strict Mode 的双调用行为不会发生,但良好的副作用设计仍至关重要。

示例:

useEffect(() => {
  const id = setInterval(() => { /* ... */ }, 1000);
  return () => clearInterval(id); // 清理确保安全
}, []);

总之,这是 React 为提升应用健壮性而引入的行为,开发者应编写更可靠的副作用逻辑,而非依赖执行次数的确定性。

发表评论 (审核通过后显示评论):

昵称:
邮箱:
内容: