深入理解 React 中 useEffect 的 cleanUp 机制

深入理解 React 中 useEffect 的 cleanUp 机制

在 React 的函数组件中,useEffect 是一个非常强大的 Hook,用于处理副作用(如数据获取、订阅或手动更改 DOM)。然而,很多人可能并不清楚 useEffect 内部是如何处理副作用的清理(cleanUp)逻辑的。本文将深入探讨 useEffect 的 cleanUp 机制,帮助你更好地理解 React 的内部实现。

一、useEffect 的基本用法

在 React 中,useEffect 是一个用于处理副作用的 Hook。它可以接受一个回调函数和一个依赖数组(deps),当依赖数组中的值发生变化时,回调函数会被重新执行。同时,useEffect 还支持返回一个清理函数(cleanUp),用于在组件卸载或副作用更新前清理之前的副作用。

例如,以下是一个简单的 useEffect 使用示例:

jsx 复制代码
import React, { useState, useEffect } from 'react';
​
function App() {
  const [count, setCount] = useState(0);
​
  useEffect(() => {
    console.log('init');
    return () => {
      console.log('cleanup 0');
    };
  }, []);
​
  useEffect(() => {
    console.log('update1', count);
    return () => {
      console.log('cleanup 1');
    };
  }, [count]);
​
  useEffect(() => {
    console.log('update2', count);
    return () => {
      console.log('cleanup 2');
    };
  }, [count]);
​
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
​
export default App;

在这个例子中,我们定义了三个 useEffect

  1. 第一个 useEffect 的依赖数组为空([]),因此它只会在组件挂载时执行一次。
  2. 第二个和第三个 useEffect 的依赖数组包含 count,因此每当 count 的值发生变化时,它们都会重新执行。

每个 useEffect 都返回了一个清理函数(cleanUp),用于在副作用更新或组件卸载时执行清理操作。

二、cleanUp 的存放与调用

React 内部通过一个复杂的机制来管理 useEffect 的副作用和清理函数。以下是一个简化版的代码示例,展示了如何在 React 的内部实现中处理 useEffect 的 cleanUp 逻辑:

jsx 复制代码
function commitEffectHooks() {
  function run(fiber) {
    if (!fiber) {
      return;
    }
​
    if (!fiber.alternate) {
      // 挂载阶段
      fiber?.effectHooks?.forEach((hook) => {
        hook.cleanup = hook.callback();
      });
    } else {
      // 更新阶段
      fiber.effectHooks?.forEach((newHook, index) => {
        if (newHook.deps.length > 0) {
          const oldEffectHook = fiber.alternate?.effectHooks[index];
​
          const needUpdate = oldEffectHook?.deps.some((oldDep, i) => {
            return oldDep !== newHook.deps[i];
          });
​
          if (needUpdate) {
            newHook.cleanup = newHook.callback();
          }
        }
      });
    }
​
    run(fiber.child);
    run(fiber.sibling);
  }
​
  function runCleanup(fiber) {
    if (!fiber) return;
    fiber.alternate?.effectHooks?.forEach((hook) => {
      if (hook.deps.length > 0) {
        hook.cleanup && hook.cleanup();
      }
    });
    runCleanup(fiber.child);
    runCleanup(fiber.sibling);
  }
​
  runCleanup(wipRoot);
  run(wipFiber);
}

三、cleanUp 的调用时机

从上述代码中可以看出,cleanUp 的调用时机主要有以下几种情况:

  1. 组件卸载时 :在组件卸载时,React 会调用 runCleanup 函数,清理所有副作用。此时,无论依赖数组是否为空,cleanUp 都会被调用。
  2. 依赖数组变化时:在更新阶段,如果依赖数组中的值发生变化,React 会调用当前副作用的回调函数,并在调用之前执行上一次副作用的 cleanUp 函数。
  3. 依赖数组为空时 :如果依赖数组为空([]),副作用只会执行一次,不会触发 cleanUp。这是因为依赖数组为空的副作用被视为"只在挂载时执行"的副作用。

四、处理依赖为空的情况

在某些情况下,我们可能需要显式地处理依赖数组为空的情况。例如,我们可以在 runCleanup 函数中添加一个额外的条件,确保即使依赖数组为空,cleanUp 也不会被调用:

jsx 复制代码
function runCleanup(fiber) {
  if (!fiber) return;
  fiber.alternate?.effectHooks?.forEach((hook) => {
    if (hook.deps.length > 0) {
      hook.cleanup && hook.cleanup();
    }
  });
  runCleanup(fiber.child);
  runCleanup(fiber.sibling);
}

通过这种方式,我们可以确保只有依赖数组不为空时,cleanUp 才会被调用。

五、总结

通过本文的介绍,我们深入探讨了 React 中 useEffect 的 cleanUp 机制。useEffect 的 cleanUp 逻辑是 React 内部实现的关键部分,它确保了副作用的正确清理和更新。理解这些机制可以帮助我们更好地使用 useEffect,避免常见的副作用问题。

相关推荐
三十而立洋26 分钟前
深入理解 npm:从核心机制到常用命令全解析
前端·前端工程化
Canace29 分钟前
最新版 Codex 工作流的问题
前端·人工智能·agent
变与不变80632 分钟前
引用类型、内存原理与对象
开发语言·前端·javascript
jearry33 分钟前
给 C++ 老程序补现代 UI:WebView2 多入口资源加载与虚拟主机名实践
前端
打呵欠的猫36 分钟前
让 AI 帮你写 Git Commit Message:从"fix bug"到语义化提交只需一个 Hook
前端·ai编程
labixiong37 分钟前
CSS 锚点定位实测:零 JS 实现气泡跟随,自动翻转很香但暗藏 3 个致命坑
前端·css
qibmz37 分钟前
用 DeepSeek 给 GitHub 仓库加 PR 自动 Review
前端
计算机魔术师38 分钟前
幻觉率从 4.2% 降到 2% 又退回,GPT-6 Astra 到底在藏什么
前端
梨想橙汁39 分钟前
Git 常用命令大全:提交、查看日志、版本回退、文件撤销
前端·javascript
Amos_Web1 小时前
Rspack 源码解析(三):从入口到依赖图,读懂 Make 阶段的 Rust 任务循环
前端·rust