第八章:useEffect源码解析

前言

这章开始介绍相关的生命周期,之前的hook都是和更新渲染、更新相关的;
useEffect 解决函数组件中的生命周期,对应类组件中,componentDidMount、componentDidUpdate、componentWillUnmount等生命周期方法;

生命周期方法的设计旨在解决一系列与组件的创建、更新和销毁相关的问题,以确保开发者有足够的控制权来执行特定的逻辑。

源码解析

js 复制代码
useEffect(setup, dependencies?)
// setup它应该返回一个 清理函数(cleanup)

mount阶段

按照常规流程,调用HooksDispatcherOnMountInDEV.useEffect 的方法,前置check检查方法,核心代码mountEffect(setup,dependencies) ;

首先调用mountWorkInProgressHook方法,创建hook对象

js 复制代码
const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
};
const Passive = 0b00000000000000100000000000;
const PassiveStatic = 0b00100000000000000000000000;
fiber.flags |= (Passive | PassiveStatic);

flags属性的作用?

在fiber上有一个flags属性,是一个标记,比如useEffect的回调setup函数不会立刻执行,在之后的某一个环节才会执行,就是通过这个标识的判断出是否执行的。

执行pushEffect(HasEffect | Passive, create, destroy, deps) 方法,pushEffect创建effect对象;

js 复制代码
// pushEffect第一个参数为tag
const HasEffect = 0b0001;
const Passive =  0b1000;
const effect = {
    tag: tag, // 关于执行任务的时机判断
    create: setup,
    destroy: undefined,
    deps: dependencies,
    next: null
};
fiber.updateQueue = {
  lastEffect: effect,
  stores: null
}  

可以看出pushEffct函数作用是为了往fiber.updateQueue.lastEffec的环状链表上创建effct对象,保存effct上的关键信息;

js 复制代码
hook.memoizedState = effect;

没有任何值返回;

js 复制代码
const ChildDeletion = 0b00000000000000000000010000;
const PassiveMask = Passive | ChildDeletion;

在commitRoot阶段,在挂载真实dom之前判断子flags或者当前flags (flags & PassiveMask) != 0 如果成立,会往任务队列中push一个任务,当前任务队列执行完之后在下一个任务队列执行flushPassiveEffects 函数,主要执行useEffect的回调函数setup和清理函数(cleanup);

第一次遍历fiber树,将要执行cleanup函数,如果 (fiber.flags & Passive) != 0 ,找到当前fiber.updateQueue.lastEffect , 找到第一个effect,开始遍历effect队列,如果 HasEffect |Passive 属于effect.tag的子集,并且effect.destroy不为空,执行effect.destroy方法;

js 复制代码
(HasEffect |Passive) & effect.tag === (HasEffect |Passive)

第二次遍历fiber树,将要执行setup函数,如果 **(fiber.flags & Passive) != 0**,找到当前*fiber.updateQueue.lastEffect* , 找到第一个effect,开始遍历effect队列,如果 **HasEffect |Passive** 属于effect.tag的子集,并且执行effect.create函数,**effect.destroy = setup()** 此时会执行setup内部的方法;

update阶段

新建fiber树的时候,

js 复制代码
const StaticMask = LayoutStatic | PassiveStatic | RefStatic;
fiber.flags &= StaticMask

调用HooksDispatcherOnUpdateInDEV.useSyncExternalStore 方法,调用check方法,核心方法updateEffectImpl(Passive, Passive, create, deps) , 第一个参数是flags,第二个参数是tag;

调用updateWorkInProgressHook方法,更新mount阶段创建的hook对象;当前deps和之前的effect.deps进行比较,如果相同,

js 复制代码
hook.memoizedState = pushEffect(Passive, create, destroy, deps)

参考上面的pushEffect方法,

如果不同,更新flags,更新memoizedState值; 如果依赖项为undefined或者null,那么会判断成更新,走以下链路;

js 复制代码
fiber.flags |= Passive;
hook.memoizedState = pushEffect(HasEffect | Passive, create, destroy, nextDeps);

判断当前的更新lane是否为1(先理解成点击事件,后面会讲lane模型的时候会细说),如果通过点击事件触发的更新渲染,在当前任务队列的最后时机,会执行setup和cleanup相关的方法;

总结

useEffect 可以模拟生命周期,在mount阶段一定会执行,在更新阶段如果依赖项有变化,那么更新就会执行,而在执行setup函数前,会调用所有fiber上的cleanup函数;

但是useEffct并不是模拟componentDidMount、componentDidUpdate、componentWillUnmount这些方法,useEffect相关的执行时机会晚于以上生命周期函数,事实上,上面的生命周期对应的是useLayoutEffct的执行时机。

useEfect执行时机在当前任务队列中最后,或者在下一个任务队列,是所有生命周期最晚的。

相关推荐
原则猫6 小时前
UDP 到底怎么实现可靠的
前端
Java后端的Ai之路6 小时前
14、ReactJS - 路由(Router)
前端·react.js·状态模式
裕晟资质规划8 小时前
三类涉密资质延续时限差异分析:军工60日与集成印制3个月的边界
java·服务器·前端·网络·安全
ShineWinsu8 小时前
对于JavaScript:JavaScript基础语法的解析
开发语言·前端·javascript·面试·web·基础语法·循环
月夏8 小时前
Vue3 转 React:18 项对照表 + 5 个必踩的坑(附 7 天上手路线)
前端
广州华水科技8 小时前
单北斗GNSS位移监测系统的应用与维护解析
前端
梦曦i9 小时前
uni-storage 0.1.0 发布:TTL过期管理+多实例隔离
前端·storage·uni-app x
猫头_10 小时前
AI 流式传输工程指南:模型吐出半截 JSON,前端如何边收边渲染?
前端·javascript·llm
console.log('npc')10 小时前
第 1 章 项目概述与环境搭建
前端·人工智能·大模型
定栓10 小时前
Webpack迁到Vite第七节(二):全家桶移除 — Babel 全家桶深度分析
前端·webpack·node.js