在使用 React 18 的并发渲染(Concurrent Rendering)特性时,为什么某些 useEffect 中的副作用可能被执行多次,如何正确处理这种情况?

在使用 React 18 的并发渲染(Concurrent Rendering)特性时,为什么某些 useEffect 中的副作用可能被执行多次,如何正确处理这种情况?

回答与解析:

React 18 引入了并发渲染(Concurrent Rendering)机制,允许 React 在渲染过程中中断、暂停或重排任务,以优先处理高优先级的更新(如用户输入)。在开发模式下,React 还启用了严格模式(Strict Mode),会故意对组件进行双重渲染(double rendering)——即 mount → unmount → mount,以此帮助开发者发现潜在的副作用问题。

这导致 useEffect 中的副作用(如数据获取、订阅、手动 DOM 操作等)可能被多次调用。虽然在生产环境中通常不会重复执行(除非并发中断后重试),但开发者仍需确保副作用是幂等的(多次执行不会导致错误或状态不一致)或正确清理。

正确处理方式包括:

  1. 使用清理函数(cleanup function):在 useEffect 中返回一个清理函数,用于取消订阅、清除定时器等,避免内存泄漏或竞态条件。

    useEffect(() => {
      const timer = setTimeout(() => {
        console.log('Executed');
      }, 1000);
      return () => clearTimeout(timer); // 清理
    }, []);
    
  2. 使用 useRef 跳过重复逻辑(谨慎使用):对于某些只应在首次挂载时执行的操作(如初始化第三方库),可通过 ref 标记是否已执行。

    const hasMounted = useRef(false);
    useEffect(() => {
      if (!hasMounted.current) {
        // 初始化逻辑
        hasMounted.current = true;
      }
    }, []);
    

    但这种做法违背了 React 的响应式原则,应尽量避免。

  3. 设计幂等的副作用:确保多次执行副作用不会产生副作用(如重复添加监听器、发送重复请求等)。

  4. 使用 useInsertionEffect(仅限 CSS-in-JS 库):对于需要同步插入样式的场景,可使用 useInsertionEffect,但它不适合一般逻辑。

总之,应遵循 React 的“副作用应可被清理和重复执行”的原则,而不是试图绕过并发渲染机制。这有助于构建更健壮、可预测的应用。

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

昵称:
邮箱:
内容: