React16源码: React中的beginWork的源码实现
beginWork
1 )概述
- 在 renderRoot 之后,要对我们的 Fiber 树每一个节点进行对应的更新
- 更新节点的一个入口方法,就是
beginWork - 这个入口方法会有帮助我们去优化整棵树的更新过程
- react 它的节点其实是非常多的,如果每一次子节点的一个更新
- 就需要每一个节点都执行一遍更新的话,它整体的性能肯定会不好,而且是没有必要的
- 我们一个子节点的更新,可能不会影响到它的兄弟节点的更新
- 所以这部分肯定是要优化的
- 具体它是如何进行优化的,如下
- 首先,要判断组件的更新是否可以优化
- 然后要根据节点的类型分发进行处理,每一个节点它的更新方式会不一样
- 最后是要根据 expirationTime 等信息判断这个节点的更新是否可以跳过
2 )源码
- 在 renderRoot 中,它里面调用一个方法叫做 workLoop
- 在 workLoop 中调用的一个方法叫做 performUnitOfWork
- 而 beginWork 方法就在 performUnitOfWork 中调用
beginWork就是执行对整棵树的每一个节点进行更新的一个操作- beginWork 来自于
import {beginWork} from './ReactFiberBeginWork';
定位到 packages/react-reconciler/src/ReactFiberBeginWork.js
整个文件 export 出去的只有 beginWork 方法
function beginWork(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime,
): Fiber | null {// 读取了 workInProgress.expirationTime 这个值,这个 expirationTime 是节点上的 expirationTime// 函数第三个参数 renderExpirationTime 是 nextExpirationTimeToWorkOn 和 下面这个 expirationTime 有区别const updateExpirationTime = workInProgress.expirationTime;// 在 ReactDOM.render 第一次渲染时,第一个节点 current 是有值的, 接下去的节点都是没有的// 因为我们操作都是在 workInProgress 上操作的// 所以,workInProgress 创建的 child 节点也是一个 workInProgress 而不是 current// performUnitOfWork 接收的就是 workInProgressif (current !== null) {const oldProps = current.memoizedProps;const newProps = workInProgress.pendingProps;// 下面 renderExpirationTime 标志优先级最大都到哪个点,如果节点上的 expirationTime 比 renderExpirationTime 小// 说明节点上有优先级更高的任务,在这次渲染里是会执行的,反之,则不需要进行渲染,这个节点可以跳过// (updateExpirationTime === NoWork || updateExpirationTime > renderExpirationTime) 代表没有更新 或 有更新,但是优先级不高// hasLegacyContextChanged 属 context 相关api, childContextType 先跳过// oldProps === newProps 表示 props 一样if (oldProps === newProps &&!hasLegacyContextChanged() &&(updateExpirationTime === NoWork ||updateExpirationTime > renderExpirationTime)) {// This fiber does not have any pending work. Bailout without entering// the begin phase. There's still some bookkeeping we that needs to be done// in this optimized path, mostly pushing stuff onto the stack.// switch 先跳过switch (workInProgress.tag) {case HostRoot:pushHostRootContext(workInProgress);resetHydrationState();break;case HostComponent:pushHostContext(workInProgress);break;case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {pushLegacyContextProvider(workInProgress);}break;}case HostPortal:pushHostContainer(workInProgress,workInProgress.stateNode.containerInfo,);break;case ContextProvider: {const newValue = workInProgress.memoizedProps.value;pushProvider(workInProgress, newValue);break;}case Profiler:if (enableProfilerTimer) {workInProgress.effectTag |= Update;}break;case SuspenseComponent: {const state: SuspenseState | null = workInProgress.memoizedState;const didTimeout = state !== null && state.didTimeout;if (didTimeout) {// If this boundary is currently timed out, we need to decide// whether to retry the primary children, or to skip over it and// go straight to the fallback. Check the priority of the primary// child fragment.const primaryChildFragment: Fiber = (workInProgress.child: any);const primaryChildExpirationTime =primaryChildFragment.childExpirationTime;if (primaryChildExpirationTime !== NoWork &&primaryChildExpirationTime <= renderExpirationTime) {// The primary children have pending work. Use the normal path// to attempt to render the primary children again.return updateSuspenseComponent(current,workInProgress,renderExpirationTime,);} else {// The primary children do not have pending work with sufficient// priority. Bailout.const child = bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);if (child !== null) {// The fallback children have pending work. Skip over the// primary children and work on the fallback.return child.sibling;} else {return null;}}}break;}}// 这个方法跳过该节点与其所有子节点的更新return bailoutOnAlreadyFinishedWork(current,workInProgress,renderExpirationTime,);}}// Before entering the begin phase, clear the expiration time.workInProgress.expirationTime = NoWork;// 如果节点有更新,通过节点的 tag,执行不同的方法,进行组件的更新switch (workInProgress.tag) {case IndeterminateComponent: {const elementType = workInProgress.elementType;return mountIndeterminateComponent(current,workInProgress,elementType,renderExpirationTime,);}case LazyComponent: {const elementType = workInProgress.elementType;return mountLazyComponent(current,workInProgress,elementType,updateExpirationTime,renderExpirationTime,);}case FunctionComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateFunctionComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}case ClassComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return updateClassComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}case HostRoot:return updateHostRoot(current, workInProgress, renderExpirationTime);case HostComponent:return updateHostComponent(current, workInProgress, renderExpirationTime);case HostText:return updateHostText(current, workInProgress);case SuspenseComponent:return updateSuspenseComponent(current,workInProgress,renderExpirationTime,);case HostPortal:return updatePortalComponent(current,workInProgress,renderExpirationTime,);case ForwardRef: {const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === type? unresolvedProps: resolveDefaultProps(type, unresolvedProps);return updateForwardRef(current,workInProgress,type,resolvedProps,renderExpirationTime,);}case Fragment:return updateFragment(current, workInProgress, renderExpirationTime);case Mode:return updateMode(current, workInProgress, renderExpirationTime);case Profiler:return updateProfiler(current, workInProgress, renderExpirationTime);case ContextProvider:return updateContextProvider(current,workInProgress,renderExpirationTime,);case ContextConsumer:return updateContextConsumer(current,workInProgress,renderExpirationTime,);case MemoComponent: {const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);return updateMemoComponent(current,workInProgress,type,resolvedProps,updateExpirationTime,renderExpirationTime,);}case SimpleMemoComponent: {return updateSimpleMemoComponent(current,workInProgress,workInProgress.type,workInProgress.pendingProps,updateExpirationTime,renderExpirationTime,);}case IncompleteClassComponent: {const Component = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =workInProgress.elementType === Component? unresolvedProps: resolveDefaultProps(Component, unresolvedProps);return mountIncompleteClassComponent(current,workInProgress,Component,resolvedProps,renderExpirationTime,);}default:invariant(false,'Unknown unit of work tag. This error is likely caused by a bug in ' +'React. Please file an issue.',);}
}
- 注意,最顶层
workInProgress.expirationTime;这个 expirationTime Fiber 节点上的 expirationTime- 对比 ReactFiberScheduler.js 中的 renderRoot 中读取的
root.nextExpirationTimeToWorkOn - 这个 root 是 FiberRoot, 而 FiberRoot 对应的 Fiber对象是 RootFiber
- RootFiber 是 FiberRoot 的 current 属性,它没有对应React组件树的任何节点, 它只对应 FiberRoot 节点
- FiberRoot 节点也就是挂载整个应用到的 Dom 节点
- FiberRoot 上面存储整个应用的 expirationTime 和
- RootFiber 的 stateNode 属性是 FiberRoot
- FiberRoot 上面会存储整个应用上面的 expirationTime 和 nextExpirationTimeToWorkOn
- 上面的两个值意义是不一样的, 在 renderRoot 函数中的一段代码如下
// Reset the stack and start working from the root. resetStack(); nextRoot = root; nextRenderExpirationTime = expirationTime; // nextUnitOfWork 是一个Fiber对象, 对应 RootFiber, 而非 FiberRoot // 更新过程使用的都是 Fiber 对象,不会是 FiberRoot nextUnitOfWork = createWorkInProgress(nextRoot.current, // 注意这里null,nextRenderExpirationTime, );- nextUnitOfWork 才是我们每一个节点去更新时要操作的节点对象
- 所以,workInProgress.expirationTime 是对应Fiber节点产生更新的过期时间
- 这里页面点击按钮让某个组件更新,最多到App上面创建更新
- 不可能到 RootFiber 上面创建更新
- 而 RootFiber 产生更新时在 ReactDOM.render 的时候才会有
- 注意,在 beginWork 第一个参数是 current 是 Fiber对象,它对应的tag是
HostRoot- 在创建 FibeRoot 时, 在 ReactFiberReconciler.js 中的 createContainer 中
- 有一个方法叫做 createFiberRoot, 定位到 ReactFiberRoot.js 中
- 定位到 createHostRootFiber 方法,而这个方法又来自于 ReactFiber.js
- 找到 createFiber, 是最终创建Fiber对象的时候调用的方法
return createFiber(HostRoot, null, null, mode); - 参数是
HostRoot, null, null, mode - 第一个参数 HostRoot 就是 Fiber的tag
- 在 ReactFiberScheduler.js 中 performUnitOfWork 接收的就是一个 workInProgress
- 它传给 beginWork 的第一个参数 current 是 workInProgress.alternate
- 在第一次渲染,也就是 ReactDOM.render 的时候, workInProgress 是刚刚创建的
- 是不会在创建一个 current, 要等渲染结束后,把 current 和 workInProgress 的指针进行一个调换
- 它才会变成有 current, 没有 workInProgress,再下次有更新产生,进行渲染时,才会创建一个新的 workInProgress
- 这时候,current 和 workInProgress 都有了
- 在最顶层 判断 current 是否 为 null, 也就是 判断是否是第一次渲染
- 对比 ReactFiberScheduler.js 中的 renderRoot 中读取的
- 关于
bailoutOnAlreadyFinishedWork这个方法帮助跳过该节点与其所有子节点的更新function bailoutOnAlreadyFinishedWork(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime, ): Fiber | null {cancelWorkTimer(workInProgress);if (current !== null) {// Reuse previous context listworkInProgress.firstContextDependency = current.firstContextDependency;}if (enableProfilerTimer) {// Don't update "base" render times for bailouts.stopProfilerTimerIfRunning(workInProgress);}// Check if the children have any pending work.const childExpirationTime = workInProgress.childExpirationTime; // 这个值是 React 16.5 加上的,更早跳过更新// 子树上无更新,或高优先级任务在这次渲染中完成的 则跳过,是一个非常大的优化if (childExpirationTime === NoWork ||childExpirationTime > renderExpirationTime) {// The children don't have any work either. We can skip them.// TODO: Once we add back resuming, we should check if the children are// a work-in-progress set. If so, we need to transfer their effects.return null;} else {// This fiber doesn't have work, but its subtree does. Clone the child// fibers and continue.// 如果子树上有更新要执行,拷贝老的childcloneChildFibers(current, workInProgress);return workInProgress.child;} }- 回过头看 performUnitOfWork, return 了 child 之后,赋值给next
let next; if (enableProfilerTimer) {// ... 跳过很多代码next = beginWork(current, workInProgress, nextRenderExpirationTime);// ... 跳过很多代码 } else {next = beginWork(current, workInProgress, nextRenderExpirationTime);workInProgress.memoizedProps = workInProgress.pendingProps; }// ... 跳过很多代码if (next === null) {// If this doesn't spawn new work, complete the current work.next = completeUnitOfWork(workInProgress); }ReactCurrentOwner.current = null; // next 不为 null, 直接 return return next; - return next; 之后 回调 workLoop, 之后赋值给 nextUnitOfWork
while (nextUnitOfWork !== null) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);} - 通过 while 循环继续执行 child 的更新
- 这就是循环能够成立的条件
- 回过头看 performUnitOfWork, return 了 child 之后,赋值给next
- 回到 beginWork 中当节点还有更新的时候,执行 switch case
- 通过节点类型执行不同的方法来进行组件的更新
- 比如 FunctionComponent, MemoComponent 等
相关文章:
React16源码: React中的beginWork的源码实现
beginWork 1 )概述 在 renderRoot 之后,要对我们的 Fiber 树每一个节点进行对应的更新更新节点的一个入口方法,就是 beginWork这个入口方法会有帮助我们去优化整棵树的更新过程 react 它的节点其实是非常多的,如果每一次子节点的…...
5-微信小程序语法参考
1. 数据绑定 官网传送门 WXML 中的动态数据均来自对应 Page 的 data。 数据绑定使用 Mustache 语法(双大括号)将变量包起来 ts Page({data: {info: hello wechart!,msgList: [{ msg: hello }, { msg: wechart }]}, })WXML <view class"vie…...
数组练习 Leetcode 566.重塑矩阵
在 MATLAB 中,有一个非常有用的函数 reshape ,它可以将一个 m x n 矩阵重塑为另一个大小不同(r x c)的新矩阵,但保留其原始数据。 给你一个由二维数组 mat 表示的 m x n 矩阵,以及两个正整数 r 和 c &#…...
Linux centos中find命令的多种用途:按照具体应用来详细说明find的用法举例
目录 一、find命令 二、find命令的语法 (一)语法格式 (二)选项 1、选项(option)介绍 2、控制符号链接的option 3、调试选项debugopts 4、优化选项 (三)表达式expression 1、选项options 2、测试…...
服务器数据恢复—OceanStor存储raid5热备盘同步数据失败的数据恢复案例
服务器数据恢复环境: 华为OceanStor某型号存储,存储内有一组由24块硬盘组建的raid5阵列,配置1块热备盘。 服务器故障: 该存储raid5阵列中有一块硬盘离线,热备盘自动激活并开始同步数据,在热备盘同步数据的…...
Xline v0.6.1: 一个用于元数据管理的分布式KV存储
Xline是什么?我们为什么要做Xline? Xline是一个基于Curp协议的,用于管理元数据的分布式KV存储。现有的分布式KV存储大多采用Raft共识协议,需要两次RTT才能完成一次请求。当部署在单个数据中心时,节点之间的延迟较低&a…...
【CSS】解决height = line-height 文字不垂直居中(偏上、偏下)的问题
解决办法1: 查看 font-family 属性,确认是否是因为字体而导致的不垂直居中问题。 其他小知识: 基线就是小写x字母的下边缘(线) 就是我们常说的 基线。line-height 属性设置的行高也就是定义的两行文字基线之间的距离! 参考文章:…...
天津想转行学python培训班靠谱吗?
现在的职业如此繁多,很多人把高薪当成衡量工作好坏的重要标准,因此IT行业以超出其他行业几倍薪资水平成为不错的选择,而Python又以其简单易学好上手成为大家所青睐的学习目标。 Python发展前景如何 Python语言就业发展方向广泛:…...
(C语言)冒泡排序
一、运行结果; 二、源代码; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//实现buble_sort函数; void buble_sort(int arr[], int sz) {//初始化变量值;int i 0;//嵌套循环冒泡排序;//外层循环&…...
怎么样的布局是符合可制造性的PCB布局?
满足可制造性、可装配性、可维修性要求,方便调试的时候于检测和返修,能够方便的拆卸器件: 1)极性器件的方向不要超过2种,最好都进行统一方向等要求,如图1-1所示; 图1-1 极性器件方向统一摆放 2…...
第28关 k8s监控实战之Prometheus(九)
------> 课程视频同步分享在今日头条和B站 大家好,我是博哥爱运维。早期我们经常用邮箱接收报警邮件,但是报警不及时,而且目前各云平台对邮件发送限制还比较严格,所以目前在生产中用得更为多的是基于webhook来转发报警内容到企…...
安全防御之可信计算技术
可信计算技术是一种计算机安全体系结构,旨在提高计算机系统在面临各种攻击和威胁时的安全性和保密性。它通过包括硬件加密、受限访问以及计算机系统本身的完整性验证等技术手段,确保计算机系统在各种攻击和威胁下保持高度安全和保密性。 一、可信计算基…...
FPGA引脚物理电平(内部资源,Select IO)-认知2
引脚电平 The SelectIO pins can be configured to various I/O standards, both single-ended and differential. • Single-ended I/O standards (e.g., LVCMOS, LVTTL, HSTL, PCI, and SSTL) • Differential I/O standards (e.g., LVDS, Mini_LVDS, RSDS, PPDS, BLVDS, and…...
PBR材质纹理下载
03:10 按照视频里的顺序 我们从第6个网站开始倒数 点击本行文字或下方链接 进入查看 6大网站地址 网址查看链接: http://www.uzing.net/community_show-1962-48-48-35.html 06 Tectures Wood Fence 001 | 3D TEXTURES 简介:最大的纹理网站之一&#x…...
mac PyCharm 使用conda环境
1 使用conda创建虚拟环境 conda create -n test6 python3.9 -y conda activate test62 选择conda环境 本地 选择已经存在的conda环境 右下角会显示现在的环境。...
10个常用的正则表达式
1 电话号码 let r1 /^1[3-9]\d{9}$/g console.log(r1.exec(18596932371)) 2 qq号 let r2 /^[1-9][0-9]{4,9}$/g console.log(r2.exec(123456)) 3 十六进制的方式表示颜色 let r3 /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g // # 可能可有可无,如果不需要#&a…...
对一手游的自定义 luajit 字节码的研究
对一手游的自定义 luajit 字节码的研究 前言 最近闲下来之后无聊研究起了一个unity手游 大量使用了 lua (或者说就是 lua 写的 ) 看到网上已有的一些针对方案 都觉得太不方便 于是深入研究了一下 他自定义的 luajit 情况研究 首先 这是一个 unity的 传…...
1125. 牛的旅行 (Floyd算法,最短路)
1125. 牛的旅行 - AcWing题库 农民John的农场里有很多牧区,有的路径连接一些特定的牧区。 一片所有连通的牧区称为一个牧场。 但是就目前而言,你能看到至少有两个牧区不连通。 现在,John想在农场里添加一条路径(注意ÿ…...
oracle “Interested Transaction List”(ITL)的概念
“Interested Transaction List”(ITL)的概念。让我们逐点理解: 块头和ITL: 每个数据库段块的块头都包含一个Interested Transaction List(ITL)。ITL用于确定数据库开始修改块时某个事务是否未提交。 ITL的…...
kali下-MSF-ftp_login模块破解FTP账号及密码
一、环境准备 两台设备在同一个网络内 一台kali系统:192.168.10.128 一台winserver2016:192.168.10.132 二、MSF介绍 metasploit 全称是The Metasploit Framework,又称MSF,是Kali 内置的一款渗透测试框架,也是全球…...
STM32F030硬件I2C避坑指南:Timing值、滤波器配置与NBYTES重加载模式详解
STM32F030硬件I2C避坑指南:Timing值、滤波器配置与NBYTES重加载模式详解 1. 深入理解I2C_Timing寄存器的计算逻辑 许多开发者在使用STM32F030硬件I2C时,往往直接套用CubeMX生成的默认值或网络上的示例代码,却对I2C_Timing寄存器的底层计算原理…...
学术论文翻译翻车重灾区!Perplexity翻译查询功能如何通过引用锚点保留+LaTeX公式智能隔离实现零失真输出(仅限Pro+订阅用户可见的隐藏模式)
更多请点击: https://intelliparadigm.com 第一章:学术论文翻译翻车重灾区的底层归因分析 学术论文翻译失准并非偶然现象,其背后存在系统性语言学、认知科学与工程实践三重张力。当非母语研究者依赖通用大模型或词典式工具进行技术文本转译时…...
RISC-V开放架构如何重塑垂直半导体商业模式
1. 从边缘到中心:RISC-V的崛起与半导体模式的裂变最近和几位在芯片设计公司工作的老朋友聊天,话题总绕不开RISC-V。十年前,当我们还在讨论ARM和x86谁主沉浮时,RISC-V还只是学术界论文里的一个概念。如今,它已经成了行业…...
理光MP C2500扫描到共享文件夹保姆级教程(附Windows 10/11权限避坑指南)
理光MP C2500扫描到共享文件夹全流程解决方案与Windows权限深度优化 办公室里那台老当益壮的理光MP C2500复合机,至今仍是许多中小企业的生产力主力。但当IT管理员尝试配置"扫描到共享文件夹"功能时,往往会遭遇浏览网络空白、权限拒绝等"…...
【独家首发】DeepSeek官方未公开的DRY检查白皮书(v2.3.1内测版):覆盖LoRA适配器、MoE路由层、Tokenizer预处理3大高危模块
更多请点击: https://codechina.net 第一章:DeepSeek DRY原则检查的演进脉络与核心定义 DRY(Don’t Repeat Yourself)作为软件工程基石性原则,在DeepSeek大模型推理与代码生成场景中已从静态语法检查逐步演化为语义感…...
猫抓插件终极指南:轻松嗅探下载网页视频音频的浏览器神器
猫抓插件终极指南:轻松嗅探下载网页视频音频的浏览器神器 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 你是否曾经遇到过这样的情况&…...
全志V853开发板音频系统实战:从ALSA驱动到应用开发全解析
1. 项目概述:从一块开发板到音频系统的构建最近在折腾百问网的100ASK_V853-PRO开发板,这块板子搭载了全志V853这颗高性能AIoT芯片,资源相当丰富。官方资料和社区讨论大多聚焦在其NPU算力、摄像头接入和图像识别上,但我在实际项目中…...
TVA智能体范式的工业视觉革命(2)
重磅预告:本专栏将独家连载系列丛书《智能体视觉技术与应用》部分精华内容,该书是世界首套系统阐述“因式智能体”视觉理论与实践的专著,特邀美国 TypeOne 公司首席科学家、斯坦福大学博士 Bohan 担任技术顾问。Bohan先生师从美国三院院士、“…...
聚合物半导体薄膜:柔性电子皮肤如何实现无感健康监测
1. 项目概述:从“硬核”到“柔韧”的健康监测革命如果你还在用那些又厚又硬、贴着皮肤半天就发痒发红的老式健康监测电极,是时候了解一下这个正在改变游戏规则的新玩意儿了——聚合物半导体薄膜。这可不是什么实验室里的遥远概念,它正从顶尖期…...
RISC-V开发板免费申请与实战指南:从环境搭建到项目移植
1. 项目概述:一次难得的RISC-V生态深度体验机会最近在开发者圈子里,一个消息引起了不小的讨论:可以免费申请到基于RISC-V架构的生态开发板。这对于我们这些常年和ARM、x86打交道的开发者来说,无疑是一个极具吸引力的“尝鲜”机会。…...
