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

相关推荐
中微子1 小时前
React 状态管理 源码深度解析
前端·react.js
加减法原则2 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js
yanlele2 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
lichenyang4532 小时前
React移动端开发项目优化
前端·react.js·前端框架
你的人类朋友2 小时前
🍃Kubernetes(k8s)核心概念一览
前端·后端·自动化运维
web_Hsir2 小时前
vue3.2 前端动态分页算法
前端·算法
烛阴3 小时前
WebSocket实时通信入门到实践
前端·javascript
草巾冒小子3 小时前
vue3实战:.ts文件中的interface定义与抛出、其他文件的调用方式
前端·javascript·vue.js
DoraBigHead3 小时前
你写前端按钮,他们扛服务器压力:搞懂后端那些“黑话”!
前端·javascript·架构
Xiaouuuuua4 小时前
一个简单的脚本,让pdf开启夜间模式
java·前端·pdf