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 内置的一款渗透测试框架,也是全球…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Golang——7、包与接口详解
包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析
Java求职者面试指南:Spring、Spring Boot、Spring MVC与MyBatis技术解析 一、第一轮基础概念问题 1. Spring框架的核心容器是什么?它的作用是什么? Spring框架的核心容器是IoC(控制反转)容器。它的主要作用是管理对…...
[USACO23FEB] Bakery S
题目描述 Bessie 开了一家面包店! 在她的面包店里,Bessie 有一个烤箱,可以在 t C t_C tC 的时间内生产一块饼干或在 t M t_M tM 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC,tM≤109)。由于空间…...

生信服务器 | 做生信为什么推荐使用Linux服务器?
原文链接:生信服务器 | 做生信为什么推荐使用Linux服务器? 一、 做生信为什么推荐使用服务器? 大家好,我是小杜。在做生信分析的同学,或是将接触学习生信分析的同学,<font style"color:rgb(53, 1…...