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;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, 也就是 判断是否是第一次渲染
  • 关于 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 的更新

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

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

相关文章

学习 SSR(Server-Side Rendering)的心得和体会

学习SSR&#xff08;Server-Side Rendering&#xff09;的心得和体会 引言 在现代的前端开发中&#xff0c;性能优化和用户体验始终是核心考量之一。而在众多优化策略中&#xff0c;服务器端渲染&#xff08;Server-Side Rendering&#xff0c;简称SSR&#xff09;是一个重要…

netty 使用证书

1创建私钥&#xff08;.key&#xff09; openssl genrsa -out server.key 2048 2基于私钥&#xff08;.key&#xff09;创建证书签名请求&#xff08;.csr&#xff09; openssl req -new -key server.key -out server.csr -config ./…/ssl.cnf 3.生成CA私钥&#xff08;ca…

【c++】栈(satck)和队列(queue)

目录 一、stack 1.stack的介绍 2.stack的使用 3.stack的模拟实现 二、queue 1.queue的介绍 2.queue的使用 3.queue的模拟实现 三、priority_queue 1.priority_queue的介绍 2.priority_queue的使用 一、stack 1.stack的介绍 &#xff08;1&#xff09;stack是一种容…

kotlin 和 java 的区别

一、什么是kotlin&#xff1f; Kotlin&#xff08;科特林&#xff09;是一个用于现代多平台应用的静态编程语言&#xff0c;由 JetBrains 开发。Kotlin可以编译成Java字节码&#xff0c;也可以编译成JavaScript&#xff0c;方便在没有JVM的设备上运行。除此之外Kotlin还可以编…

pytest -- 基本使用详解

1. pytest基本介绍 pytest 是一个功能强大且易于使用的 Python 测试框架&#xff0c;用于编写单元测试、集成测试和功能测试。 它提供了丰富的功能和灵活的用法&#xff0c;使得编写和运行测试变得简单而高效。 --------------->>>>> pytest框架优点&#xff1a…

Git中config配置

文章目录 简介一、config级别二、config基本配置 简介 Git是一个开源的分布式版本控制系统&#xff0c;用于处理各种规模的项目版本管理。它由Linus Torvalds设计&#xff0c;主要用于Linux内核开发。Git的特点包括速度、简单的设计、对非线性开发模式的支持、完全的分布式能力…

springboot(ssm仓库管理系统 wms智能仓储管理系统Java系统

springboot(ssm仓库管理系统 wms智能仓储管理系统Java系统 开发语言&#xff1a;Java 框架&#xff1a;ssm/springboot vue JDK版本&#xff1a;JDK1.8&#xff08;或11&#xff09; 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 5.7&#xff08;或8.0&#xff09; …

企业怎么传输大容量视频?

在企业中&#xff0c;视频的应用越来越广泛&#xff0c;不论是在内部沟通、培训、宣传&#xff0c;还是在外部合作、推广、展示方面&#xff0c;视频都扮演着不可或缺的角色。然而&#xff0c;由于视频文件通常较大&#xff0c;传输时往往会面临网速慢、容量限制、安全风险等问…

flutter 五点一:MaterialApp Theme

ThemeData factory ThemeData({bool? applyElevationOverlayColor, //material2的darkTheme下 增加一个半透明遮罩 来凸显阴影效果 material3下无效 貌似没啥用NoDefaultCupertinoThemeData? cupertinoOverrideTheme, //ios组件样式 Iterable<ThemeExtension<dyn…

【Java基础_01】Java运行机制及运行过程

【Java基础_01】Java运行机制及运行过程 文章目录 【Java基础_01】Java运行机制及运行过程1.Java 运行机制及运行过程1.1 Java 核心机制-Java 虚拟机 [JVM java virtual machine] 1.2 JDK&#xff0c;JRE1.3 JVM,JDK和JRE1.4 环境变量path1.4.1 为什么要配置path1.4.2 配置环…

C++中的三元运算符(也称为条件运算符)是一种简洁的语法,用于基于一个布尔条件表达式选择两个值中的一个。

文章目录 用法举例&#xff1a;另一个例子&#xff1a;注意事项&#xff1a; C中的三元运算符&#xff08;也称为条件运算符&#xff09;是一种简洁的语法&#xff0c;用于基于一个布尔条件表达式选择两个值中的一个。三元运算符的一般形式是&#xff1a; condition ? expr1 :…

集中常见的排序方法Go语言版本实现

简单排序&#xff1a;插入排序、选择排序、 冒泡排序 分治排序&#xff1a;快速排序、归并排序 分配排序&#xff1a;桶排序、基数排序 树状排序&#xff1a;堆排序 其他&#xff1a;计数排序、希尔排序 稳定排序&#xff1a;如果 a 原本在 b 的前面&#xff0c;且 a b&#x…

Mesh自组网通信技术概述

Mesh自组网核心技术 Mesh自组网&#xff08;Mesh Networking&#xff09;是一种网络技术&#xff0c;主要用于在多个节点之间建立动态的、自我管理的网络连接。这种技术的核心在于其自我组织和自我修复的能力&#xff0c;使得网络能够在节点移动或节点故障时自动调整。以下是Me…

2024.1.17 用户画像day02 - Elastic Search

目录 ES和数据库的类比 ELK集中日志协议栈介绍 ES的介绍 ES的架构 ES中的名词 ES中的角色 分片与副本的区别在于: MYSQL分库与分表: 倒排序索引: ES写入数据原理: ES读取、检索数据原理: 重点: ES 的架构 , ES读写的原理 ES和数据库的类比 关系型数据库非关系型数…

mysql进阶-索引进阶

目录 1. 索引的相关语法 1.1 创建索引&#xff1a; 1.2 删除索引&#xff1a; 1.3 其他修改或创建方法&#xff1a; 2. 索引创建分类 2.1 索引类型 2.2 索引方法 2.3 索引分类 3. 索引原则 3.1 覆盖索引 3.2 最左前缀原则 3.3 索引下推(index condition pushdown) …

C++发展史

目录 什么是C C的发展史 C的重要性 C在实际工作中的应用 “21天教你学会C” 什么是C 先看看祖师爷&#xff0c;记得多拜拜&#x1f92d; C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的 程序&#xff0c;需要高度…

逻辑卷管理、逻辑卷扩展、文件系统刷新、逻辑卷删除、VDO、RAID磁盘阵列、查看进程命令、进程控制、进程管理

1 打开虚拟机 2 环境准备&#xff1a;添加一块新的80G硬盘 [rootlocalhost ~]# lsblk 80G硬盘进行&#xff08;MBR分区模式&#xff09;规划分区 划分3个10G的主分区;2个20G的逻辑分区 [rootlocalhost ~]# fdisk /dev/vdb n 创建主分区--->回车--->回车--->回车…

Vue3 + Electron框架读取程序外部配置文件

网上找了一堆都不行&#xff0c;根据这个步骤来肯定能用 1. 在项目下新建一个config.json文件 2. json文件中写入一些配置 3. vue.config.js中配置打包时把config.json文件copy到应用目录下 pluginOptions:{electronBuilder:{nodeIntegration:true,builderOptions: {extraReso…

MySQL表的基本插入查询操作详解

博学而笃志&#xff0c;切问而近思 文章目录 插入插入更新 替换查询全列查询指定列查询查询字段为表达式查询结果指定别名查询结果去重 WHERE 条件基本比较逻辑运算符使用LIKE进行模糊匹配使用IN进行多个值匹配 排序筛选分页结果更新数据删除数据截断表聚合函数COUNTSUMAVGMAXM…

怎样实现安全便捷的网间数据安全交换?

数据安全交换是指在数据传输过程中采取一系列措施来保护数据的完整性、机密性和可用性。网间数据安全交换&#xff0c;则是需要进行跨网络、跨网段甚至跨组织地进行数据交互&#xff0c;对于数据的传输要求会更高。 大部分企业都是通过网闸、DMZ区、VLAN、双网云桌面等方式实现…