第九章:useLayoutEffect源码解析

前言

useLayoutEffect 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
useLayoutEffectuseEffect 的一个版本,在浏览器重新绘制屏幕之前触发。

源码解析

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

mount阶段

按照常规流程,调用HooksDispatcherOnMountInDEV.useLayoutEffect 的方法,前置check检查方法,核心方法mountLayoutEffect

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

js 复制代码
const hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null
};
fiber.memoizedState = hook;
const LayoutStatic = 0b00010000000000000000000000;
const Update = 0b00000000000000000000000100;
fiber.flags = Update | LayoutStatic;

useEffect 代码类似,有区别的是flags的值,也是通过flags进行标记,后续在合适的时机处理这个setup 函数的; 执行pushEffect(HasEffect | hookFlags, create, undefined, nextDeps) 方法,用于创建effect对象;

js 复制代码
const Layout =  0b0100;
const HasEffect = 0b0001;
const effect = {
    tag: Layout | HasEffect,
    create: create,
    destroy: destroy,
    deps: deps,
    // Circular
    next: null
 };
 fiber.updateQueue = { 
     lastEffect: effect, 
     stores: null 
 }

pushEffect 方法函数作用是为了往fiber.updateQueue.lastEffec的环状链表上创建effct对象,和useEffect处理逻辑一致,唯一区别是tag的标记值;

在commitRoot阶段,首先执行执行的时机是在当前任务队列中,在commitLayoutEffects 方法中执行,这个方法是在commitMutationEffects(真实dom挂载的处理) 下面执行的;

判断是否有组件中是否有useLayoutEffect,判断条件为

js 复制代码
const Callback = 0b00000000000000000001000000;
const Ref = 0b00000000000000001000000000;
const Visibility = 0b00000000000010000000000000;
const LayoutMask = Update | Callback | Ref | Visibility;
fiber.flags & LayoutMask !== 0

如果成立,找到fiber.updateQueue中第一个effect对象,进行判断条件

js 复制代码
const flags = Layout | HasEffect;
flags & effect.tag === flags;

如果条件成立,会执行effect.destroy = effect.create() 循环遍历effect链表,符合条件的都会执行;

update阶段

熟悉一下新建fiber树的条件。

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

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

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

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

如果不同,

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

pushEffect 参考上面

在commitRoot阶段,执行effect.create 时机时一样的,判断条件和mount阶段一致;

effect.destroy 执行时机和effect.create不一致,顺序执行

js 复制代码
// 处理dom
commitMutationEffects(root, finishedWork, lanes);
// 处理useLayoutEffect方法
commitLayoutEffects(finishedWork, root, lanes);

commitMutationEffects 方法中,遍历到Function的fiber节点中,在执行commitReconciliationEffects(真实dom挂载) 方法之后,执行commitHookEffectListUnmount 方法;遍历updateQueue链表

js 复制代码
commitHookEffectListUnmount(Layout | HasEffect, finishedWork, finishedWork.return);
// commitHookEffectListUnmount
if(flags & effect.tag === flags){
    effect.destroy();
};

然后再执行commitLayoutEffects 方法,判断是否需要执行effect.create方法,和mount阶段一致;

总结

上一章说到useEffect 可以模拟生命周期,但是useLayoutEffect 执行的时机才对应componentDidMountcomponentDidUpdatecomponentWillUnmount这些方法;
useLayoutEffectuseEffect 写法一致,使用相同,只不过在处理时机不同而已。
useLayoutEffect 会比useEffect执行时机更靠前,有一些操作dom的情况可以放在这里,能够更好的优化体验。

相关推荐
SEO_juper9 分钟前
新独立站冷启动收录全攻略:配置、推送、抓取配额优化完整手册
前端·谷歌·seo·跨境电商·外贸·geo·独立站
TinssonTai14 分钟前
这个 VS Code 插件让我的 AI Coding 又快又稳 - 旧瓶装新酒
前端·人工智能·程序员
体验家14 分钟前
体验家 XMPlus 网页端问卷 SDK 技术解析:用几行 JavaScript 实现精准场景触发与防打扰机制
开发语言·前端·javascript
Maimai1080829 分钟前
Web3 前端交易系统如何落地:从下单 UI 到 Operation 编码、签名与实时状态更新
前端·react.js·ui·架构·前端框架·web3
Cxiaomu29 分钟前
React接入WebRTC实时视频实践
react.js·音视频·webrtc
kidding72338 分钟前
高效备忘清单工具类小程序
前端·计算机网络·微信小程序·小程序
IMPYLH1 小时前
HTML 的 <abbr> 元素
前端·算法·html
李白的天不白1 小时前
Tree-Shaking
前端
Csvn2 小时前
TypeScript:你以为安全的 `JSON.parse` 其实是颗雷 — 运行时类型安全实战
前端·javascript
橘子星2 小时前
深入理解线性数据结构:栈、队列与链表
前端·javascript