react源码解析10.commit阶段

news/2024/4/26 20:52:12/文章来源:https://blog.csdn.net/js_2028/article/details/129237591

在render阶段的末尾会调用commitRoot(root);进入commit阶段,这里的root指的就是fiberRoot,然后会遍历render阶段生成的effectList,effectList上的Fiber节点保存着对应的props变化。之后会遍历effectList进行对应的dom操作和生命周期、hooks回调或销毁函数,各个函数做的事情如下

react源码10.1

在commitRoot函数中其实是调度了commitRootImpl函数

//ReactFiberWorkLoop.old.js
function commitRoot(root) {var renderPriorityLevel = getCurrentPriorityLevel();runWithPriority$1(ImmediatePriority$1, commitRootImpl.bind(null, root, renderPriorityLevel));return null;
}

在commitRootImpl的函数中主要分三个部分:

  • commit阶段前置工作

    1. 调用flushPassiveEffects执行完所有effect的任务

    2. 初始化相关变量

    3. 赋值firstEffect给后面遍历effectList用

//ReactFiberWorkLoop.old.js
do {// 调用flushPassiveEffects执行完所有effect的任务flushPassiveEffects();} while (rootWithPendingPassiveEffects !== null);//...// 重置变量 finishedWork指rooFiberroot.finishedWork = null;//重置优先级root.finishedLanes = NoLanes;// Scheduler回调函数重置root.callbackNode = null;root.callbackId = NoLanes;// 重置全局变量if (root === workInProgressRoot) {workInProgressRoot = null;workInProgress = null;workInProgressRootRenderLanes = NoLanes;} else {}//rootFiber可能会有新的副作用 将它也加入到effectLislet firstEffect;if (finishedWork.effectTag > PerformedWork) {if (finishedWork.lastEffect !== null) {finishedWork.lastEffect.nextEffect = finishedWork;firstEffect = finishedWork.firstEffect;} else {firstEffect = finishedWork;}} else {firstEffect = finishedWork.firstEffect;}
  • mutation阶段

    ​ 遍历effectList分别执行三个方法commitBeforeMutationEffects、commitMutationEffects、commitLayoutEffects执行对应的dom操作和生命周期

    ​ 在介绍双缓存Fiber树的时候,我们在构建完workInProgress Fiber树之后会将fiberRoot的current指向workInProgress Fiber,让workInProgress Fiber成为current,这个步骤发生在commitMutationEffects函数执行之后,commitLayoutEffects之前,因为componentWillUnmount发生在commitMutationEffects函数中,这时还可以获取之前的Update,而componentDidMountcomponentDidUpdate会在commitLayoutEffects中执行,这时已经可以获取更新后的真实dom了

function commitRootImpl(root, renderPriorityLevel) {//...do {//...commitBeforeMutationEffects();} while (nextEffect !== null);do {//...commitMutationEffects(root, renderPriorityLevel);//commitMutationEffects} while (nextEffect !== null);root.current = finishedWork;//切换current Fiber树do {//...commitLayoutEffects(root, lanes);//commitLayoutEffects} while (nextEffect !== null);//...
}
  • mutation 后

    1. 根据rootDoesHavePassiveEffects赋值相关变量

    2. 执行flushSyncCallbackQueue处理componentDidMount等生命周期或者useLayoutEffect等同步任务

const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;// 根据rootDoesHavePassiveEffects赋值相关变量
if (rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = false;rootWithPendingPassiveEffects = root;pendingPassiveEffectsLanes = lanes;pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {}
//...// 确保被调度
ensureRootIsScheduled(root, now());// ...// 执行flushSyncCallbackQueue处理componentDidMount等生命周期或者useLayoutEffect等同步任务
flushSyncCallbackQueue();return null;

现在让我们来看看mutation阶段的三个函数分别做了什么事情

  • commitBeforeMutationEffects
    该函数主要做了如下两件事

    1. 执行getSnapshotBeforeUpdate

      在源码中commitBeforeMutationEffectOnFiber对应的函数是commitBeforeMutationLifeCycles在该函数中会调用getSnapshotBeforeUpdate,现在我们知道了getSnapshotBeforeUpdate是在mutation阶段中的commitBeforeMutationEffect函数中执行的,而commit阶段是同步的,所以getSnapshotBeforeUpdate也同步执行

function commitBeforeMutationLifeCycles(current: Fiber | null,finishedWork: Fiber,
): void {switch (finishedWork.tag) {//...case ClassComponent: {if const instance = finishedWork.stateNode;const snapshot = instance.getSnapshotBeforeUpdate(//getSnapshotBeforeUpdatefinishedWork.elementType === finishedWork.type? prevProps: resolveDefaultProps(finishedWork.type, prevProps),prevState,);}
}
  1. 调度useEffect
    在flushPassiveEffects函数中调用flushPassiveEffectsImpl遍历pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount,执行对应的effect回调和销毁函数,而这两个数组是在commitLayoutEffects函数中赋值的(待会就会讲到),mutation后effectList赋值给rootWithPendingPassiveEffects,然后scheduleCallback调度执行flushPassiveEffects

相关参考视频讲解:进入学习

function flushPassiveEffectsImpl() {if (rootWithPendingPassiveEffects === null) {//在mutation后变成了rootreturn false;}const unmountEffects = pendingPassiveHookEffectsUnmount;pendingPassiveHookEffectsUnmount = [];//useEffect的回调函数for (let i = 0; i < unmountEffects.length; i += 2) {const effect = ((unmountEffects[i]: any): HookEffect);//...const destroy = effect.destroy;destroy();}const mountEffects = pendingPassiveHookEffectsMount;//useEffect的销毁函数pendingPassiveHookEffectsMount = [];for (let i = 0; i < mountEffects.length; i += 2) {const effect = ((unmountEffects[i]: any): HookEffect);//...const create = effect.create;effect.destroy = create();}
}
 componentDidUpdate或componentDidMount会在commit阶段同步执行(这个后面会讲到),而useEffect会在commit阶段异步调度,所以适用于数据请求等副作用的处理> 注意,和在render阶段的fiber node会打上Placement等标签一样,useEffect或useLayoutEffect也有对应的effect Tag,在源码中对应export const Passive = /* */ 0b0000000001000000000;
function commitBeforeMutationEffects() {while (nextEffect !== null) {const current = nextEffect.alternate;const effectTag = nextEffect.effectTag;// 在commitBeforeMutationEffectOnFiber函数中会执行getSnapshotBeforeUpdateif ((effectTag & Snapshot) !== NoEffect) {commitBeforeMutationEffectOnFiber(current, nextEffect);}// scheduleCallback调度useEffectif ((effectTag & Passive) !== NoEffect) {if (!rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = true;scheduleCallback(NormalSchedulerPriority, () => {flushPassiveEffects();return null;});}}nextEffect = nextEffect.nextEffect;//遍历effectList}
}
  • commitMutationEffects commitMutationEffects主要做了如下几件事

    1. 调用commitDetachRef解绑ref(第11章hook会讲解)

    2. 根据effectTag执行对应的dom操作

    3. useLayoutEffect销毁函数在UpdateTag时执行

function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {//遍历effectListwhile (nextEffect !== null) {const effectTag = nextEffect.effectTag;// 调用commitDetachRef解绑refif (effectTag & Ref) {const current = nextEffect.alternate;if (current !== null) {commitDetachRef(current);}}// 根据effectTag执行对应的dom操作const primaryEffectTag =effectTag & (Placement | Update | Deletion | Hydrating);switch (primaryEffectTag) {// 插入domcase Placement: {commitPlacement(nextEffect);nextEffect.effectTag &= ~Placement;break;}// 插入更新domcase PlacementAndUpdate: {// 插入commitPlacement(nextEffect);nextEffect.effectTag &= ~Placement;// 更新const current = nextEffect.alternate;commitWork(current, nextEffect);break;}//...// 更新domcase Update: {const current = nextEffect.alternate;commitWork(current, nextEffect);break;}// 删除domcase Deletion: {commitDeletion(root, nextEffect, renderPriorityLevel);break;}}nextEffect = nextEffect.nextEffect;}}
 现在让我们来看看操作dom的这几个函数**commitPlacement插入节点:**简化后的代码很清晰,找到该节点最近的parent节点和兄弟节点,然后根据isContainer来判断是插入到兄弟节点前还是append到parent节点后
function commitPlacement(finishedWork: Fiber): void {//...const parentFiber = getHostParentFiber(finishedWork);//找到最近的parentlet parent;let isContainer;const parentStateNode = parentFiber.stateNode;switch (parentFiber.tag) {case HostComponent:parent = parentStateNode;isContainer = false;break;//...}const before = getHostSibling(finishedWork);//找兄弟节点if (isContainer) {insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);} else {insertOrAppendPlacementNode(finishedWork, before, parent);}}
 **commitWork更新节点:**在简化后的源码中可以看到​ 如果fiber的tag是SimpleMemoComponent会调用commitHookEffectListUnmount执行对应的hook的销毁函数,可以看到传入的参数是HookLayout | HookHasEffect,也就是说执行useLayoutEffect的销毁函数。​ 如果是HostComponent,那么调用commitUpdate,commitUpdate最后会调用updateDOMProperties处理对应Update的dom操作
function commitWork(current: Fiber | null, finishedWork: Fiber): void {if (!supportsMutation) {switch (finishedWork.tag) {//...case SimpleMemoComponent: {commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);}//...}}switch (finishedWork.tag) {//...case HostComponent: {//...commitUpdate(instance,updatePayload,type,oldProps,newProps,finishedWork,);}return;}}
function updateDOMProperties(domElement: Element,updatePayload: Array<any>,wasCustomComponentTag: boolean,isCustomComponentTag: boolean,): void {// TODO: Handle wasCustomComponentTagfor (let i = 0; i < updatePayload.length; i += 2) {const propKey = updatePayload[i];const propValue = updatePayload[i + 1];if (propKey === STYLE) {setValueForStyles(domElement, propValue);} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {setInnerHTML(domElement, propValue);} else if (propKey === CHILDREN) {setTextContent(domElement, propValue);} else {setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);}}}
 **commitDeletion删除节点:**如果是ClassComponent会执行componentWillUnmount,删除fiber,如果是FunctionComponent 会删除ref、并执行useEffect的销毁函数,具体可在源码中查看unmountHostComponents、commitNestedUnmounts、detachFiberMutation这几个函数
function commitDeletion(finishedRoot: FiberRoot,current: Fiber,renderPriorityLevel: ReactPriorityLevel,): void {if (supportsMutation) {// Recursively delete all host nodes from the parent.// Detach refs and call componentWillUnmount() on the whole subtree.unmountHostComponents(finishedRoot, current, renderPriorityLevel);} else {// Detach refs and call componentWillUnmount() on the whole subtree.commitNestedUnmounts(finishedRoot, current, renderPriorityLevel);}const alternate = current.alternate;detachFiberMutation(current);if (alternate !== null) {detachFiberMutation(alternate);}}
  • commitLayoutEffects 在commitMutationEffects之后所有的dom操作都已经完成,可以访问dom了,commitLayoutEffects主要做了

    1. 调用commitLayoutEffectOnFiber执行相关生命周期函数或者hook相关callback

    2. 执行commitAttachRef为ref赋值

function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {while (nextEffect !== null) {const effectTag = nextEffect.effectTag;// 调用commitLayoutEffectOnFiber执行生命周期和hookif (effectTag & (Update | Callback)) {const current = nextEffect.alternate;commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);}// ref赋值if (effectTag & Ref) {commitAttachRef(nextEffect);}nextEffect = nextEffect.nextEffect;}}
 **commitLayoutEffectOnFiber:**​ 在源码中commitLayoutEffectOnFiber函数的别名是commitLifeCycles,在简化后的代码中可以看到,commitLifeCycles会判断fiber的类型,SimpleMemoComponent会执行useLayoutEffect的回调,然后调度useEffect,ClassComponent会执行componentDidMount或者componentDidUpdate,this.setState第二个参数也会执行,HostRoot会执行ReactDOM.render函数的第三个参数,例如
ReactDOM.render(<App />, document.querySelector("#root"), function() {console.log("root mount");});
 现在可以知道useLayoutEffect是在commit阶段同步执行,useEffect会在commit阶段异步调度
function commitLifeCycles(finishedRoot: FiberRoot,current: Fiber | null,finishedWork: Fiber,committedLanes: Lanes,): void {switch (finishedWork.tag) {case SimpleMemoComponent: {// 此函数会调用useLayoutEffect的回调commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);// 向pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount中push effect                        // 并且调度它们schedulePassiveEffects(finishedWork);}case ClassComponent: {//条件判断...instance.componentDidMount();//条件判断...instance.componentDidUpdate(//update 在layout期间同步执行prevProps,prevState,instance.__reactInternalSnapshotBeforeUpdate,);      }case HostRoot: {commitUpdateQueue(finishedWork, updateQueue, instance);//render第三个参数}}}
 在schedulePassiveEffects中会将useEffect的销毁和回调函数push到pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount中
function schedulePassiveEffects(finishedWork: Fiber) {const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;if (lastEffect !== null) {const firstEffect = lastEffect.next;let effect = firstEffect;do {const {next, tag} = effect;if ((tag & HookPassive) !== NoHookEffect &&(tag & HookHasEffect) !== NoHookEffect) {//push useEffect的销毁函数并且加入调度enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);//push useEffect的回调函数并且加入调度enqueuePendingPassiveHookEffectMount(finishedWork, effect);}effect = next;} while (effect !== firstEffect);}}
 **commitAttachRef:**​ commitAttacRef中会判断ref的类型,执行ref或者给ref.current赋值
function commitAttachRef(finishedWork: Fiber) {const ref = finishedWork.ref;if (ref !== null) {const instance = finishedWork.stateNode;let instanceToUse;switch (finishedWork.tag) {case HostComponent:instanceToUse = getPublicInstance(instance);break;default:instanceToUse = instance;}if (typeof ref === "function") {// 执行ref回调ref(instanceToUse);} else {// 如果是值的类型则赋值给ref.currentref.current = instanceToUse;}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_74709.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】知识点总结(C语言)

线性表、栈和队列、串、数组和广义表、树和二叉树、图、查找、排序线性表线性表&#xff08;顺序表示&#xff09;线性表是具有相同特性元素的一个有限序列&#xff0c;数据元素之间是线性关系&#xff0c;起始元素称为线性起点&#xff0c;终端元素称为线性终点。线性表的顺序…

sed 功能详解

介绍sedsed是一种流编辑器&#xff0c;它一次处理一行内容&#xff0c;把当前处理的行存储在临时缓冲区中&#xff08;buffer&#xff09;,称为"模式空间"&#xff0c;接着sed命令处理缓冲区中的内容&#xff0c;处理完成后&#xff0c;把缓冲区的内容送往屏幕&#…

RCEE: Event Extraction as Machine Reading Comprehension 论文解读

RCEE: Event Extraction as Machine Reading Comprehension 论文&#xff1a;Event Extraction as Machine Reading Comprehension (aclanthology.org) 代码&#xff1a;jianliu-ml/EEasMRC (github.com) 期刊/会议&#xff1a;EMNLP 2020 摘要 事件提取(Event extraction,…

哪个品牌蓝牙耳机性价比高?性价比高的平价蓝牙耳机推荐

现如今&#xff0c;随着蓝牙技术的进步&#xff0c;蓝牙耳机在人们日常生活中的便捷性更胜从前。越来越多的蓝牙耳机品牌被大众看见、认可。那么&#xff0c;哪个品牌的蓝牙耳机性价比高&#xff1f;接下来&#xff0c;我给大家推荐几款性价比高的平价蓝牙耳机&#xff0c;一起…

软件测试面试问答

笔试 笔试的话我们需要揣测具体会考什么内容&#xff0c;我们可以通过招聘信息去了解该公司需要什么样的技能&#xff0c;以此来准备笔试。一般必考的内容会有编程&#xff0c;测试用例设计&#xff0c;工作流程&#xff0c;逻辑思维等内容&#xff0c;除此之外每个公司可能还会…

移动端监听物理返回

业务场景&#xff1a;用户没有填完数据却不小心点到了回退按钮&#xff0c;此时需要展示确认弹框项目场景&#xff1a;vue2 uni-app Chrome Dev调试工具代码片段&#xff1a;onLoad(options){// 将当前url地址添加到浏览器的历史记录中window.history.pushState(null, null, …

OSI和TCP/IP网络模型细讲

文章目录一、OSI七层参考模型二、TCP/IP体系结构三、TCP/IP参考模型四、沙漏计时器形状的TCP/IP协议族五、两种国际标准对比相似之处不同之处一、OSI七层参考模型 OSI参考模型共分为7层&#xff0c;低三层面向通信&#xff0c;可用软硬件实现&#xff1b;高三层面向信息处理&am…

一个基于 LKM 的 Linux 内核级 rootkit 的实现

博客已迁移至&#xff1a;https://gls.show/ GitHub链接 演示Slides overview rootkit是一种恶意软件&#xff0c;攻击者可以在获得 root 或管理员权限后安装它&#xff0c;从而隐藏入侵并保持root权限访问。rootkit可以是用户级的&#xff0c;也可以是内核级的。关于rootk…

Android 实现菜单拖拽排序

效果图简介本文主角是ItemTouchHelper。它是RecyclerView对于item交互处理的一个「辅助类」&#xff0c;主要用于拖拽以及滑动处理。以接口实现的方式&#xff0c;达到配置简单、逻辑解耦、职责分明的效果&#xff0c;并且支持所有的布局方式。功能拆解功能实现4.1、实现接口自…

ARM的工作模式和37个寄存器

一、ARM的工作模式 ARM一共有7种工作模式 模式含义User非特权模式&#xff0c;大部分任务执行在这种模式FIQ当一个高优先级&#xff08;fast) 中断产生时将会进入这种模式IRQ当一个低优先级&#xff08;normal) 中断产生时将会进入这种模式Supervisor当复位或软中断指令执行时…

CISP注册信息安全专业人员证书

一、什么是“CISP”&#xff1f; 注册信息安全专业人员(Certified Information Security Professional&#xff0c;简称“CISP”)&#xff0c;是安全行业最为权威的安全资格认证&#xff0c;由中国信息安全测评中心统一授权组织&#xff0c;中国信息安全测评中心授权培训机构进…

GMP洁净净化车间布局建设|喜格净化设计建设

GMP洁净净化车间布局建设方案应该根据具体的生产流程、工艺要求和产品特点进行设计。以下喜格SICOLAB基本的设计原则和注意事项&#xff1a;&#xff08;1&#xff09;设计洁净度级别&#xff1a;根据产品特点和生产工艺要求&#xff0c;确定洁净度级别&#xff0c;一般分为100…

OpenCV 图像轮廓检测

本文是OpenCV图像视觉入门之路的第15篇文章&#xff0c;本文详细的介绍了图像轮廓检测的各种操作&#xff0c;例如&#xff1a;轮廓检索模式、轮廓逼近算子等操作。 图像轮廓是具有相同颜色或灰度的连续点的曲线&#xff0c;轮廓在形状分析和物体的检测和识别中很有用。图像轮廓…

2023年鞋服配饰行业如何玩转全域经营?

2023年&#xff0c;鞋服配饰行业私域已进入深水区&#xff0c;这就对私域运营提出了更高的挑战和目标&#xff0c;企业纷纷发力以私域为基石、以消费者为核心的全域经营。 不过&#xff0c;虽然鞋服配饰行业私域起步早&#xff0c;玩法多。但在迈向全域经营的过程中&#xff0…

IntelliJ插件开发教程之新建项目

JetBrains公司系列产品IDEA、WebStrom、PyCharm、CLion、GoLand等都是基于IntelliJ Platform开发而成&#xff0c;掌握IntelliJ插件开发技能便能拥有提升开发效率的终极武器。本教程Demo源码请微信公众号“开发效率”进行获取。阅读原文如果您是JetBrains产品的用户&#xff0c…

【打卡】图分析与节点嵌入

背景介绍 图&#xff08;Graphs&#xff09;是一种对物体&#xff08;objects&#xff09;和他们之间的关系&#xff08;relationships&#xff09;建模的数据结构&#xff0c;物体以结点&#xff08;nodes&#xff09;表示&#xff0c;关系以边&#xff08;edges&#xff09;…

【数电基础】——数制和码制

目录 1.概述 1.信号&#xff08;电路&#xff09;的功能 2.信号的分类&#xff1a; 3.数字信号的输入和输出的逻辑关系表示方法 2.数制 1.十进制&#xff08;D/d&#xff09; 2.二进制(B/b) 3.八进制&#xff08;O/o&#xff09; 4.十六进制&#xff08;H/h&#xff09;…

腾讯TIM实现即时通信 v3+ts实践

目录 初始化sdk 功能描述 初始化 准备 SDKAppID 调用初始化接口 监听事件 发送消息 创建消息 创建文本消息 登录登出 功能描述 登录 登出 销毁 登录设置 获取会话列表 功能描述 获取会话列表 获取全量的会话列表 历史消息 功能描述 拉取消息列表 分页拉取…

C++ Primer Plus 第6版 读书笔记(2)第2章 开始学习 C++

C是在 C 语言基础上开发的一种集面向对象编程、泛型编程和过程化编程于一体的编程语言&#xff0c;是C语言的超集。本书是根据2003年的ISO/ANSI C标准编写的&#xff0c;通过大量短小精悍的程序详细而全面地阐述了 C的基本概念和技术&#xff0c;并专辟一章介绍了C11新增的功能…

Telnet 基础实验2: SSH 实验

Telnet 基础实验2&#xff1a; SSH 实验 本实验只能使用 eNSP中 AR 系统的路由器做 拓扑图 SSH &#xff1a; Secure Shell 是一个网络安全协议&#xff0c;基本于 TCP 协议 22 端口传输数据&#xff0c;通过对网络数据的加密&#xff0c;使其能够在一个不安全的网络环境中&a…