深入理解 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,避免常见的副作用问题。

相关推荐
xiaoyan201511 分钟前
2025最新款Electron38+Vite7+Vue3+ElementPlus电脑端后台系统Exe
前端·vue.js·electron
梅孔立12 分钟前
本地多版本 Node.js 切换指南:解决 Vue nodejs 等项目版本冲突问题
前端·vue.js·node.js
小红14 分钟前
从乱码到清晰:深入理解字符编码的演进(ASCII到UTF-8)
前端
卓码软件测评33 分钟前
K6的CI/CD集成在云原生应用的性能测试应用
前端·功能测试·测试工具·ci/cd·云原生
JordanHaidee1 小时前
【Rust GUI开发入门】编写一个本地音乐播放器(11. 支持动态明暗主题切换)
前端·ui kit
爱泡脚的鸡腿1 小时前
VUE移动端项目跟练2(简洁易懂)
前端·javascript·vue.js
拜晨1 小时前
用 MCP 把自己的接口接入 AI
前端·node.js
古夕1 小时前
技术复盘文档:解决 `watchEffect` 导致的图片闪烁无限循环问题
前端·javascript·vue.js
拾缘1 小时前
esm和cmj混用报错分析
前端·javascript
古夕1 小时前
技术复盘文档:`resourceLogoUrl` 数据丢失问题分析与最终解决方案
前端·javascript·vue.js