【React源码 - Fiber架构之Renderer】

前言

本文主要将的是Fiber架构三核心中渲染器Renderer,在Reconciler调度器中“归”过程回到rootfiber节点并执行完之后会调用commitroot并传入fiberRootNode来进入到Renderer阶段(commit阶段),在commit阶段会遍历effectList来进行DOM操作,在该阶段我将其细分为三个小阶段:

  • Before Mutation: DOM操作之前
  • Mutation: DOM操作
  • Layout: DOM操作之后

EffectList是一个单向链表,在Reconciler递归中执行completeWork时构建,最终形成一个以rootfiber.firstEffect为起点的单向链表,其他fiber.updateQueue以key,vakue的数组形式保存了需要更新的props

Before Mutation

这个阶段主要是遍历effectList并执行commitBeforeMutationEffects(旧版)/commitBeforeMutationEffectsImpl(新版), 主要做了一下三件事:

  • 处理DOM渲染/删除的autoFocus、blur逻辑
  • 调用getSnapshotBeforeUpdate生命周期钩子
  • 通过scheduleCallback调度useEffect
function commitBeforeMutationEffects() {while (nextEffect !== null) {const current = nextEffect.alternate;if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {// ...focus blur相关}const effectTag = nextEffect.effectTag;// 调用getSnapshotBeforeUpdateif ((effectTag & Snapshot) !== NoEffect) {commitBeforeMutationEffectOnFiber(current, nextEffect);}// 调度useEffectif ((effectTag & Passive) !== NoEffect) {if (!rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = true;scheduleCallback(NormalSchedulerPriority, () => {flushPassiveEffects();return null;});}}nextEffect = nextEffect.nextEffect;}
}

commitBeforeMutationEffectOnFiber是commitBeforeMutationLifeCycles的别名,主要是生命周期的调用。

getSnapshotBeforeUpdate

从Reactv16开始,componentWillXXX钩子前增加了UNSAFE_前缀。原因是16用FIber架构重写,将原来的Stack Reconciler改为Fiber Reconciler后,render阶段可能会执行多次,而componentWillXXX钩子都存在于render阶段,这就会导致重复执行,设想如果我们在支付的时候,执行了多次,那就玩大了,所以进行了标记。为了解决这个问题,React提供了一个新的钩子getSnapshotBeforeUpdate,它可以保存DOM更新前的信息快照,然后给到componentDidUpdate. 并且它是在commit阶段执行的,不会重复执行。

调度useEffect

// 调度useEffectif ((effectTag & Passive) !== NoEffect) { if (!rootDoesHavePassiveEffects) {rootDoesHavePassiveEffects = true;scheduleCallback(NormalSchedulerPriority, () => {flushPassiveEffects();return null;});}}

代码中可见,scheduleCallback方法由Scheduler模块提供,用于以某个优先级异步调度一个回调函数。而flushPassiveEffects就是用来调度useEffect的。

Mutation

该阶段主要是来执行DOM的一些操作,主要也是遍历EffectList根据fiber的tag来处理不同的逻辑并更新对应的ref,主要做了以下事情:

  • 根据ContentReset effectTag重置文字节点
  • 更新ref
  • 根据effectTag(flags)分别处理,其中effectTag包括(Placement | Update | Deletion | Hydrating)
function   commitMutationEffects(root: FiberRoot, renderPriorityLevel) {// 遍历effectListwhile (nextEffect !== null) { const effectTag = nextEffect.effectTag;// 根据 ContentReset effectTag重置文字节点if (effectTag & ContentReset) {commitResetTextContent(nextEffect);}// 更新refif (effectTag & Ref) {const current = nextEffect.alternate;if (current !== null) {commitDetachRef(current);}}// 根据 effectTag 分别处理const primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating); switch (primaryEffectTag) { // 插入DOMcase Placement: {commitPlacement (nextEffect);nextEffect.effectTag &= ~Placement;break;}// 插入DOM 并 更新DOMcase PlacementAndUpdate: {// 插入commitPlacement(nextEffect);nextEffect.effectTag &= ~Placement;// 更新const current = nextEffect.alternate;commitWork(current, nextEffect);break;}// SSRcase Hydrating: { nextEffect.effectTag &= ~Hydrating;break;}// SSRcase HydratingAndUpdate: {nextEffect.effectTag &= ~Hydrating;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;`case HostComponent: {const instance: Instance = finishedWork.stateNode;if (instance != null) {// Commit the work prepared earlier.const newProps = finishedWork.memoizedProps;// For hydration we reuse the update path but we treat the oldProps// as the newProps. The updatePayload will contain the real change in// this case.const oldProps = current !== null ? current.memoizedProps : newProps;const type = finishedWork.type;// TODO: Type the updateQueue to be specific to host components.const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);finishedWork.updateQueue = null;if (updatePayload !== null) {commitUpdate(instance,updatePayload,type,oldProps,newProps,finishedWork,);}}`}}nextEffect = nextEffect.nextEffect;}
}

Placement Effect

当fiber.effectTag为Placement时,则通过commitPlacement函数,进行DOM的插入操作。

  • 获取父级DOM节点。其中finishedWork为传入的Fiber节点
  • 获取Fiber节点的DOM兄弟节点
  • 根据DOM兄弟节点是否存在决定调用parentNode.insertBefore或parentNode.appendChild执行DOM插入操作。

Update Effect

当Fiber节点含有Update effectTag,意味着该Fiber节点需要更新。调用的方法为commitWork,他会根据Fiber.tag分别处理。

case HostComponent: {const instance: Instance = finishedWork.stateNode;if (instance != null) { // Commit the work prepared earlier.const newProps = finishedWork.memoizedProps;// For hydration we reuse the update path but we treat the oldProps// as the newProps. The updatePayload will contain the real change in// this case.const oldProps = current !== null ? current.memoizedProps : newProps;const type = finishedWork.type;// TODO: Type the updateQueue to be specific to host components.const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);finishedWork.updateQueue = null;if (updatePayload !== null) {commitUpdate(instance,updatePayload,type,oldProps,newProps,finishedWork,);}}

当fiber.tag为HostComponent,会调用commitUpdate。最终会在updateDOMProperties 中将render阶段 completeWork 中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。

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);}}
}

Deletion effect

当Fiber节点含有Deletion effectTag,意味着该Fiber节点对应的DOM节点需要从页面中删除。调用的方法为commitDeletion。

  • 递归调用Fiber节点及其子孙Fiber节点中fiber.tag为ClassComponent的componentWillUnmount 生命周期钩子,从页面移除Fiber节点对应DOM节点
  • 解绑ref
  • 调度useEffect的销毁函数

Layout

该阶段之所以称为layout,因为该阶段的代码都是在DOM修改完成(mutation阶段完成)后执行的。该阶段触发的生命周期钩子和hook可以直接访问到已经改变后的DOM,即该阶段是可以参与DOM layout的阶段。

注意:由于 JS 的同步执行阻塞了主线程,所以此时 JS 已经可以获取到新的DOM,但是浏览器对新的DOM并没有完成渲染。

同before mutation、mutation阶段一样,都是遍历effectList来,并执行对应函数来进行不同的处理,这里调用的是commitLayoutEffects,主要做了两件事:

  • commitLayoutEffectOnFiber(调用生命周期钩子和hook相关操作)
  • commitAttachRef(赋值 ref)

commitLayoutEffectOnFiber

commitLayoutEffectOnFiber是commitLifeCycles的别名

function   commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {while (nextEffect !== null) {const effectTag = nextEffect.effectTag;// 调用生命周期钩子和hookif (effectTag & (Update | Callback)) {const current = nextEffect.alternate;commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);}// 赋值refif (effectTag & Ref) {commitAttachRef(nextEffect);}nextEffect = nextEffect.nextEffect;}
}

在commitLifeCycles函数中,当fiber.tag为ClassComponent时,会通过current === null?区分是mount还是update,调用componentDidMount 或componentDidUpdate 。

 case ClassComponent: {const instance = finishedWork.stateNode;if (finishedWork.flags & Update) {if (current === null) {if (enableProfilerTimer &&enableProfilerCommitHooks &&finishedWork.mode & ProfileMode) {try {startLayoutEffectTimer();instance.componentDidMount();} finally {recordLayoutEffectDuration(finishedWork);}} else {instance.componentDidMount();}}

以下函数也会在commitLifeCycles中调用:

  • 触发状态更新的this.setState如果赋值了第二个参数回调函数
  • 对于FunctionComponent及相关类型,他会调用useLayoutEffect hook的回调函数,调度useEffect的销毁与回调函数
  • 对于HostRoot,即rootFiber,如果赋值了第三个参数回调函数,也会在此时调用

commitAttachRef

commitAttachRef函数就比较简单,就是获取DOM实例,并更新ref

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;}// Moved outside to ensure DCE works with this flagif (enableScopeAPI && finishedWork.tag === ScopeComponent) {instanceToUse = instance;}if (typeof ref === 'function') {ref(instanceToUse);} else {ref.current = instanceToUse;}}
}

至此,commit阶段也完成了,这时候再将双缓存树的切换fiberRootNode的current指向current Fiber树,就可以更新试图。

Q&A

Q: useEffect和useLayoutEffect的区别?
A:useLayoutEffect从上一次更新的销毁函数调用(mutation阶段)到本次更新的回调函数调用(layout阶段)是同步执行的。
而useEffect则需要先在before mutation调度,在Layout阶段完成后再异步执行。

参考文档

React技术揭秘

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

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

相关文章

计算机毕业设计-----ssm+mysql医药进销存系统

功能介绍 医药进销存系统&#xff0c;主要功能包括&#xff1a; 公告管理&#xff1a;发布公告、公告列表&#xff1b; 生产管理&#xff1a;订单列表、增加生产、订单日志&#xff1b; 分店采购&#xff1a;分店审核、采购&#xff1b; 总店仓库&#xff1a;出库管理、仓库列…

2024 Midjourney 基础教程(⼆):了解 Midjourney Bot 和AI绘画使用技巧进阶教学

在上⼀篇⽂章中&#xff0c;我们学到了如何注册 Midjourney &#xff0c;开通付费订阅&#xff0c;并画出了可能是⾃⼰的第⼀张 AI绘画。怎么样&#xff1f;这种将想象的画⾯&#xff0c;变为现实世界图⽚的感觉。 是否有种造物者的错觉&#xff0c;同时有种开盲盒的惊喜感&…

市域治理一体化综合指挥平台解决方案:PPT全文42页,附下载

关键词&#xff1a;市域社会治理&#xff0c;智慧网格&#xff0c;市域社会治理现代化&#xff0c;智慧网格综合管理平台&#xff0c;市域治理 一、市域治理&#xff08;智慧网格&#xff09;一体化建设需求分析 1、职能部门需求&#xff1a;职能部门在市域治理中发挥着主导作…

关于谷歌浏览器如何将背景换为黑色,字体换为白色

一.关于chorme浏览器如何换色 #跟着我一起来看一下吧 操作步骤 步骤 1.在谷歌的搜索框里搜索此网址——chrome://flags/ 如图 2.访问此网址 如图 3.我们在搜索框搜索:Auto Dark Mode for Web Contents 如图 4.开启Web内容的自动暗模式 选择enbled 5.确认重新启动 选择…

126.(leaflet篇)leaflet松散型arcgis缓存切片加载

地图之家总目录(订阅之前必须详细了解该博客) arcgis缓存切片数据格式如下: 完整代码工程包下载,运行如有问题,可“私信”博主。效果如下所示: leaflet松散型arcgis缓存切片加载 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYP

2.2.3机器学习—— 判定梯度下降是否收敛 + α学习率的选择

2.2.3 判定梯度下降是否收敛 α学习率的选择 2.1、 判定梯度下降是否收敛 有两种方法&#xff0c;如下图&#xff1a; 方法一&#xff1a; 如图&#xff0c;随着迭代次数的增加&#xff0c;J(W,b)损失函数不断下降当 iterations 300 之后&#xff0c;下降的就不太明显了 / …

赋能智慧农业生产,基于YOLOv3开发构建农业生产场景下油茶作物成熟检测识别系统

AI赋能生产生活场景&#xff0c;是加速人工智能技术落地的有利途径&#xff0c;在前文很多具体的业务场景中我们也从实验的角度来尝试性地分析实践了基于AI模型来助力生产生活制造相关的各个领域&#xff0c;诸如&#xff1a;基于AI硬件实现农业作物除草就是一个比较熟知的场景…

paddle指定运行gpu

在PaddlePaddle中指定使用GPU进行运行非常简单。首先&#xff0c;确保你的机器上已经安装了CUDA和cuDNN&#xff0c;并且正确配置了GPU环境。然后&#xff0c;按照以下步骤进行操作&#xff1a; 导入PaddlePaddle库&#xff1a; import paddle设置使用的设备为GPU&#xff1a…

【数字图像处理】水平翻转、垂直翻转

图像翻转是常见的数字图像处理方式&#xff0c;分为水平翻转和垂直翻转。本文主要介绍 FPGA 实现图像翻转的基本思路&#xff0c;以及使用紫光同创 PGL22G 开发板实现数字图像水平翻转、垂直翻转的过程。 目录 1 水平翻转与垂直翻转 2 FPGA 布署与实现 2.1 功能与指标定义 …

Amazing OpenAI API:把非 OpenAI 模型都按 OpenAI API 调用

分享一个有趣的小工具&#xff0c;10MB 身材的小工具&#xff0c;能够将各种不同的模型 API 转换为开箱即用的 OpenAI API 格式。 让许多依赖 OpenAI API 的软件能够借助开发者能够接触到的&#xff0c;非 OpenAI 的 API 私有部署和使用起来。 写在前面 这个小工具软件写于两…

ChatGPT知名开源项目有哪些

ChatGPT-Next-Web&#xff1a;基于ChatGPT API的私有化部署网页聊天系统 主要功能&#xff1a; 只需在 1 分钟内即可在 Vercel 上一键免费部署&#xff0c;支持私有服务器快速部署&#xff0c;支持使用私有域名支持ChatGPT3.5、4等常见模型Linux/Windows/MacOS 上的紧凑型客户…

Ribbon学习思维导图

参考资料 1、OpenFeign与Ribbon源码分析总结与面试题 2、万字剖析OpenFeign整合Ribbon实现负载均衡的原理 3、扒一扒Nacos、OpenFeign、Ribbon、loadbalancer组件协调工作的原理 4、OpenFeign原来是这么基于Ribbon来实现负载均衡的

第18集《佛法修学概要》

戊五、结示法要 请大家打开讲义第四十四页。我们讲因果同时&#xff0c;借缘显现。 从因果转变的角度&#xff0c;佛教是说“罪从心起将心忏&#xff0c;心若灭时罪亦亡。”那么我们要知道&#xff0c;业是怎么来的&#xff1f;怎么会有业&#xff1f;为什么苹果掉下来&#x…

DEATHNOTE: 1

首先通过kali使用nmap进行主机发现 发现IP地址为192.168.75.129的主机 发现其22端口和80端口开放 对其进行详细扫描发现其操作系统是Linux 4.15 - 5.6 访问192.168.75.129&#xff1a;80会重定向到另一个页面 修改一下kali的/ect/hosts的添加一句192.168.75.129 deathnote.v…

【Vue3】2-8 : 条件渲染与列表渲染及注意点

本书目录&#xff1a;点击进入 一、条件渲染 - v-if 表达式 1.1 真值与假值 1.2 v-if &#xff0c;v-else-if &#xff0c;v-else 1.3 实战&#xff1a;isShow1 为真值时 显示 bbbbb &#xff1e; 代码 &#xff1e; 效果 二、列表渲染 - v-for 2.1 渲染 - 数组 &…

Spring基于注解的AOP控制事务

首先在.xml中开启sprong对注解事务的支持 applicationContext.xml <tx:annotation-driven transaction-manager"transactionManager"/> 然后再Service中加上注解 service Service Transactional(readOnlytrue,propagation Propagation.SUPPORTS) public cl…

聊聊PowerJob的IdGenerateService

序 本文主要研究一下PowerJob的IdGenerateService IdGenerateService tech/powerjob/server/core/uid/IdGenerateService.java Slf4j Service public class IdGenerateService {private final SnowFlakeIdGenerator snowFlakeIdGenerator;private static final int DATA_CE…

Vue-11、Vue计算属性

Vue计算属性是Vue实例的属性&#xff0c;用来根据已有的数据进行计算得到新的数据。计算属性的值会根据它的依赖缓存起来&#xff0c;在依赖没有发生改变时直接返回缓存的值&#xff0c;提高了性能。 计算属性的定义方式为在Vue实例中使用computed关键字&#xff0c;并将计算属…

Guava:Range 区间范围工具

简介 Range 表示一个间隔或一个序列。它被用于获取一组数字/串在一个特定范围之内。可比较类型的区间API&#xff0c;包括连续和离散类型。 Range 定义了连续跨度的范围边界&#xff0c;这个连续跨度是一个可以比较的类型(Comparable type)。比如1到100之间的整型数据。 在数…

安全防御之备份恢复技术

随着计算机和网络的不断普及&#xff0c;人们更多的通过网络来传递大量信息。在网络环境下&#xff0c;还有各种各样的病毒感染、系统故障、线路故障等&#xff0c;使得数据信息的安全无法得到保障。由于安全风险的动态性&#xff0c;安全不是绝对的&#xff0c;信息系统不可能…