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

相关推荐
小喷友34 分钟前
第 6 章:API 路由(后端能力)
前端·react.js·next.js
像素之间37 分钟前
elementui中rules的validator 用法
前端·javascript·elementui
小高00741 分钟前
🚀把 async/await 拆成 4 块乐高!面试官当场鼓掌👏
前端·javascript·面试
CF14年老兵42 分钟前
SQL 是什么?初学者完全指南
前端·后端·sql
2401_837088501 小时前
AJAX快速入门 - 四个核心步骤
前端·javascript·ajax
一月是个猫1 小时前
前端工程化之Lint工具链
前端
小潘同学1 小时前
less 和 sass的区别
前端
无羡仙1 小时前
当点击链接不再刷新页面
前端·javascript·html
王小发1011 小时前
快速知道 canvas 来进行微信网页视频无限循环播放的思路
前端
雲墨款哥1 小时前
为什么我的this.name输出了空字符串?严格模式与作用域链的微妙关系
前端·javascript·面试