React16源码: React中的unwindWork的源码实现

unwindWork


1 )概述

  • renderRoot 的 throw Exception 里面, 对于被捕获到错误的组件进行了一些处理
  • 并且向上去寻找能够处理这些异常的组件,比如说 class component 里面具有
  • getDerivedStateFromError 或者 componentDidCatch 这样的生命周期方法
  • 这个class component 就代表它可以处理它的子树当中渲染出来的任何的错误
  • 但是在这个过程当中,只是在上面增加了一些 SideEffect
    • 比如说, 在出错的那个组件上,增加了 Incomplete
    • 而对于能够处理这个错误的组件,增加了 ShouldCapture
  • 这些 SideEffect 最终会被如何进行处理, 这时候就要用到 unwindWork 了
  • 类似于 completeWork,对于不同组件, 进行一些不同的处理
    • 它整个流程是跟 completeWork 完全区分开的
    • 它当然也要去做一些 completWork 里面会做的一些工作
    • 但是它们肯定会有一些区别,不然它们也不需要进行一个区分
  • 对于 ShouldCapture 组件会设置 DidCapture 副作用

2 )源码

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L1354

定位到 throwException

throwException(root,returnFiber,sourceFiber, // 报错的那个组件thrownValue,nextRenderExpirationTime,
);
nextUnitOfWork = completeUnitOfWork(sourceFiber);
continue;
  • 进入 throwException 下面是精简版,只看结构

    function throwException(root: FiberRoot,returnFiber: Fiber,sourceFiber: Fiber,value: mixed,renderExpirationTime: ExpirationTime,
    ) {// 添加 Incomplete// The source fiber did not complete.sourceFiber.effectTag |= Incomplete;// Its effect list is no longer valid.// 清空 Effect 链sourceFiber.firstEffect = sourceFiber.lastEffect = null;// ... 其他代码忽略
    
  • 进入 completeUnitOfWork 下面是精简版,只看结构

    function completeUnitOfWork(workInProgress: Fiber): Fiber | null {while (true) {// ... 跳过很多代码// 符合这个条件,走的是 completeWorkif ((workInProgress.effectTag & Incomplete) === NoEffect) {// This fiber completed.if (enableProfilerTimer) {// ... 跳过很多代码nextUnitOfWork = completeWork(current,workInProgress,nextRenderExpirationTime,);// ... 跳过很多代码} else {nextUnitOfWork = completeWork(current,workInProgress,nextRenderExpirationTime,);}// ... 跳过很多代码} else {// ... 跳过很多代码// 否则,走的是 unwindWork, 对于不同的组件,这个返回值也会不同const next = unwindWork(workInProgress, nextRenderExpirationTime);// ... 跳过很多代码if (next !== null) {// ... 跳过很多代码next.effectTag &= HostEffectMask; // 注意这个运算,只有在 当前effect和HostEffectMask共有的,才会最终留存下来return next; // 看到这边 return 了 next}if (returnFiber !== null) {// Mark the parent fiber as incomplete and clear its effect list.returnFiber.firstEffect = returnFiber.lastEffect = null;returnFiber.effectTag |= Incomplete;}}}return null;
    }
    
    • next.effectTag &= HostEffectMask 这个运算中,这边可能会有一个问题
    • Incomplete 是在 sourceFiber 上面,也就是说报错的那个组件上面的
    • 但是它向上寻找的就是能够处理错误的组件, 它增加的是 ShouldCapture
    • 需要注意的一点是,一开始进来处理的第一个组件,它是报错的那个组件,它并不一定是能够处理错误的那个组件
    • 因为在 unwindWork 里面,增加这个 ShouldCapture 的时候
    • 是我们在一个循环中向报错的那个组件的父链上面去寻找,可以处理错误的那个组件
    • 一般它是 HostRoot 或者是 ClassComponent
    • 所以一进来的时候处理的组件是报错那个组件,它可能不是一个ClassComponent,或者它没有错误处理的能力
    • 这个时候它不一定会进来这个 next !== null 的这个判断,那么它会往下走
    • 那往下走的时候,它判断了returnfivever不等于诺的情况
    • 它会去给 returnFiber 增加这个 Incomplete 的 effectTag
    • 也就是说如果一个子树当中的组件报错了,对于它父链上的所有组件的 completeUnitOfWork
    • 都会执行对应的 unwindWork 的流程,而不会走 completeWork的流程
    • 进入 unwindWork
      function unwindWork(workInProgress: Fiber,renderExpirationTime: ExpirationTime,
      ) {switch (workInProgress.tag) {// 在这里面,它根据不同的组件类型处理了这些内容// 它主要处理的就是 ClassComponent, HostComponent SuspenseComponent // 剩下的一些基本上都是跟 completeWork 里面类似// 对于这些组件,它跟 completeWork 最大的区别就是它会去判断 ShouldCapture 这个 SideEffect// 如果我们这个组件上面有 ShouldCapture 这个 SideEffect// 那么它会把 ShouldCapture 给它去掉, 然后增加这 DidCapture 这个 SideEffect// 这就是对于 classComponent 跟 completeWork 里面的一个最主要的区别case ClassComponent: {const Component = workInProgress.type;if (isLegacyContextProvider(Component)) {popLegacyContext(workInProgress);}const effectTag = workInProgress.effectTag;if (effectTag & ShouldCapture) {// 注意这里workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;return workInProgress;}return null;}// 与 ClassComponent 类似case HostRoot: {popHostContainer(workInProgress);popTopLevelLegacyContextObject(workInProgress);const effectTag = workInProgress.effectTag;invariant((effectTag & DidCapture) === NoEffect,'The root failed to unmount after an error. This is likely a bug in ' +'React. Please file an issue.',);// 注意这里workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;return workInProgress;}case HostComponent: {popHostContext(workInProgress);return null;}case SuspenseComponent: {const effectTag = workInProgress.effectTag;if (effectTag & ShouldCapture) {workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture;// Captured a suspense effect. Set the boundary's `alreadyCaptured`// state to true so we know to render the fallback.const current = workInProgress.alternate;const currentState: SuspenseState | null =current !== null ? current.memoizedState : null;let nextState: SuspenseState | null = workInProgress.memoizedState;if (nextState === null) {// No existing state. Create a new object.nextState = {alreadyCaptured: true,didTimeout: false,timedOutAt: NoWork,};} else if (currentState === nextState) {// There is an existing state but it's the same as the current tree's.// Clone the object.nextState = {alreadyCaptured: true,didTimeout: nextState.didTimeout,timedOutAt: nextState.timedOutAt,};} else {// Already have a clone, so it's safe to mutate.nextState.alreadyCaptured = true;}workInProgress.memoizedState = nextState;// Re-render the boundary.return workInProgress;}return null;}case HostPortal:popHostContainer(workInProgress);return null;case ContextProvider:popProvider(workInProgress);return null;default:return null;}
      }
      
      • 注意,ClassComponent和HostRoot的return内容
      • 对于HostRoot来说,所有情况下都 return workInProgress
      • 而对于 ClassComponent 在有 ShouldCapture 的时候,return的是当前 workInProgress, 否则是return null
  • 还是拿出之前的图来说这个整体流程

  • 比如说,上面这张图里面 List 这个组件它渲染的时候报错了
  • 但是它没有 getDerivedStateFromError 或者 componentDidCatch 这样的生命周期方法
  • 那么它是不会增加 ShouldCapture 这个 SideEffect 的
  • 如果这个时候,App组件具有 getDerivedStateFromError 或者 componentDidCatch
  • 这个时候 App 增加的是 ShouldCapture 这个 SideEffect
  • 在 throw Exception 处理完之后,要执行的 completeUnitOfWork 第一个节点是 List 这个组件
    • 也就是说它执行的时候,它里面的 next 是 null,会继续往下面的 returnFiber 判断中去
    • 得到的 returnFiber 是 div, 对 div 增加了这些 SideEffect 之后
  • 它又会走 completeUnitOfWork 之后,仍然走的是 unwindWork
  • 这时的 div 是一个 HostComponent,它不会处理错误,然后又走到下一个
  • 走到 App,到 unwindWork 之后,发现它是有 ShouldCapture 这个 SideEffect 的
  • 它 return 的是 workInProgress,之后有next了,这边就可以 return next
  • 并且它上面会具有 DidCapture 的 SideEffect
  • 它 return next 之后,对于 completeUnitOfWork 相当于是return了一个Fiber对象
  • 也就是说 renderRoot 处理异常后面 completeUnitOfWork 返回的 nextUnitOfWork 对象
  • 就变成了App组件它对应的Fiber对象,也就是说,nextUnitOfWork 现在等于App
  • 我们的 do while 循环仍然要继续,依然继续调用 workLoop
  • 调用 workLoop 就会调用 performUnitOfWork,然后调用 beginWork
  • 所以对于 App 这个组件,又需要去重新走一遍更新的流程
  • 但是走更新的流程的时候,这个时候已经不一样了,不一样在哪里呢?
    • App它是一个 ClassComponent,所以我们要走的是 updateClassComponent
    • 在 ReactFiberBeginWork.js 中,找到 updateClassComponent
    • 这边正常走下来,其实都是差不多的,创建这些流程之类的
    • 这边需要注意的是,比如说 updateClassInstance
    • 它对应的是在 ReactFiberClassComponent.js 里面,找到这个方法
    • 在这个方法里面,它会去 processUpdateQueue
    • 在按 unwindWork的时候,给ClassComponent 创建了一个update 叫 createClassErrorUpdate
    • 这个 update 它会去调用 getDerivedStateFromError,以及它会有一个callback是调用 componentDidCatch
    • 所以如果有 getDerivedStateFromError 这个方法,对应的在 ClassComponent 里面
    • 在进行 processUpdateQueue 的时候,肯定会调用这个方法
    • 它就会计算出有错误的情况下它的一个state
    • 这个state就会引导 ClassComponent 去渲染错误相关的UI
    • 这就是我们的组件 ClassComponent 去捕获错误,并且去渲染出错误相关UI的一个流程
    • 因为渲染的是错误相关的UI, 所以原先的它的子树肯定是不会再被渲染出来的
    • 或者有可能会被渲染出来, 这个情况视具体的内容而定
  • 在这种情况下,再回到 beginWork,再往下调用 finishClassComponent 的时候
  • 有一个判断是 if ( didCaptureError && typeof Component.getDerivedStateFromError !== 'function') {}
    • didCaptureError 来自于我们这个组件上面是否有 DidCapture 这个 SideEffect
    • 在有错误的情况下,它这个属性肯定是 true
    • 并且没有 getDerivedStateFromError 的情况,先不管,继续往下看
  • 下面的一个判断 if (current !== null && didCaptureError) {}
    • 它会执行的一个方法是 forceUnmountCurrentAndReconcile
    • 这个情况就是最常见的一个情况,就是组件是在一个更新的过程当中
    • 然后它的子树出现了错误,这个App要去渲染错误,它这边的 didCaptureError 就是 true
      function forceUnmountCurrentAndReconcile(current: Fiber,workInProgress: Fiber,nextChildren: any,renderExpirationTime: ExpirationTime,
      ) {// This function is fork of reconcileChildren. It's used in cases where we// want to reconcile without matching against the existing set. This has the// effect of all current children being unmounted; even if the type and key// are the same, the old child is unmounted and a new child is created.//// To do this, we're going to go through the reconcile algorithm twice. In// the first pass, we schedule a deletion for all the current children by// passing null.workInProgress.child = reconcileChildFibers(workInProgress,current.child,null,renderExpirationTime,);// In the second pass, we mount the new children. The trick here is that we// pass null in place of where we usually pass the current child set. This has// the effect of remounting all children regardless of whether their their// identity matches.workInProgress.child = reconcileChildFibers(workInProgress,null,nextChildren,renderExpirationTime,);
      }
      
      • 1 )它先调用了一遍 reconcileChildFibers, 先看下它的构造结构
         function reconcileChildFibers(returnFiber: Fiber,currentFirstChild: Fiber | null,newChild: any,expirationTime: ExpirationTime,): Fiber | null {}```* 这边需要注意的是, 它传入的 newChild 是 null
        * 就是说它要强制把目前所有的子树的节点全部给它删掉
        * 它渲染出没有子树的 ClassComponent
        
      • 2 )然后再渲染一遍
        • 这个时候传入的 currentFirstChild (老的 children) 是 null
        • 然后传入的 newChild 是 nextChildren
          • 因为这个 nextchildren 是我们已经从新的(有错误的)update里面, 计算出的一个新的 state
          • 这个children, 一般来说,它跟老的 children 是完全不一样的
          • 就算不是完全不一样,也可能是大部分不一样
          • 它强制在第一次的时候直接用 null, 把它的子树给清空,然后渲染新的 children
        • 这样的效率会更高一点,就不需要通过key去对比这些流程了
  • 这就是在react 它的 error boundary 这个功能
    • 提供我们 ClassComponent , 使用 getDerivedStateFromError 或者 componentDidCatch
    • 这样的生命周期方法来处理,捕获到错误之后的一个流程
  • 同样的, 这是 unwindWork 处理流程当中需要注意的一些点

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

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

相关文章

QT发生弹出警告窗口

QTC开发程序弹出警告窗口&#xff0c;如上图 实施代码&#xff1a; #include <QMessageBox> int main() {// 在发生错误的地方QMessageBox::critical(nullptr, "错误", "发生了一个错误&#xff0c;请检查您的操作。");}上面的文字可以更改&#x…

【学网攻】 第(5)节 -- Cisco VTP的使用

文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 -- 交换机配置聚合端口【学网攻】 第(4)节 -- 交换机划分Vlan 前言 网络已经成为了我们生活中不可或缺的一部分&#xff0c;它连接了世界各地的人们&#xff0c;让信息和资…

社区信息员灾情上报系统-计算机毕业设计源码13263

摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作…

初识Docker(架构、安装Docker)

一、什么是Docker Docker 是一个开源的应用容器引擎&#xff0c;它允许开发者将应用程序及其依赖打包到一个轻量级、可移植的容器中。这些容器可以在不同的计算平台上运行&#xff0c;如Linux和Windows&#xff0c;并且可以实现虚拟化。Docker 的设计目标是提供一种快速且轻量…

【数据类型转换】C语言中的数据类型转换

1.定义 数据类型转换&#xff0c;听这个名字你就懂了&#xff0c;就是将数据从一种类型转换为另一种类型。 2.自动类型转换 自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换&#xff0c;这种转换不需要程序员干预&#xff0c;会自动发生。比如说&#xff1a…

redis-发布缓存

一.redis的发布订阅 什么 是发布和订阅 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。 Redis的发布和订阅 客户端订阅频道发布的消息 频道发布消息 订阅者就可…

第四篇【传奇开心果短博文系列】Python的OpenCV库技术点案例示例:机器学习

传奇开心短博文系列 系列短博文目录Python的OpenCV库技术点案例示例系列短博文 短博文目录一、项目目标二、OpenCV机器学习介绍三、OpenCV支持向量机示例代码四、OpenCV支持向量机示例代码扩展五、OpenCVK均值聚类示例代码六、OpenCVK均值聚类示例代码扩展七、OpenCV决策树示例…

编译PCL Qt程序

使用PCL的qt程序时&#xff0c;提示不是用QVTK编译的&#xff0c;所以需要在编译VTK时打开Qt的编译选项&#xff08;由于CMakeList比较复杂&#xff0c;使用CMakeGui进行配置&#xff0c;PCL同理&#xff09;&#xff0c;编译VTK完成后&#xff0c;编译PCL也需要配置Qt支持&…

【前端web入门第一天】02 HTML图片标签 超链接标签

文章目录: 1.HTML图片标签 1.1 图像标签-基本使用1.2 图像标签-属性1.3 路径 1.3.1 相对路径 1.3.2 绝对路径 2.超链接标签 3.音频标签 4.视频标签 1.HTML图片标签 1.1 图像标签-基本使用 作用:在网页中插入图片。 <img src"图片的URL">src用于指定图像…

Python + Selenium —— 网页元素定位之Xpath定位!

前面讲的定位方式&#xff0c;都能够很方便的定位到网页元素。但是这些属性并非所有的网页元素都具备&#xff0c;可以这么说&#xff0c;绝大部分情况下都很难保证元素具备这些属性。 也就是很多时候需要使用其他的方式来定位&#xff0c;在 WebDriver 中提供了 Xpath 和 Css…

二叉树堆的应用实例分析:堆排序 | TOP-K问题

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C语言进阶之路 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 前言一、堆排序1.1 排序思想1.2 堆排序过程&#xff08;图解&#xff09;1.3 堆排序代…

IP数据云:实战网络安全的得力利器

在当今数字化时代&#xff0c;企业和个人面临着日益复杂和频繁的网络安全威胁。为了应对这些挑战&#xff0c;IP数据云作为一项全面的网络安全解决方案&#xff0c;已经在多个实际案例中展现了其卓越的能力。 1、识别并隔离异常行为 挑战&#xff1a;一家大型金融机构发现其内…

Github 不能访问,提示:port 22: Connection timed out

问题描述 github clone 代码出现错误&#xff1a; $ git clone gitgithub.com:Atlan4/Fnirsi1013D.git Cloning into Fnirsi1013D... ssh: connect to host github.com port 22: Connection timed out fatal: Could not read from remote repository.Please make sure you ha…

Unity 外观模式(实例详解)

文章目录 示例1&#xff1a;初始化游戏场景中的多个子系统示例2&#xff1a;管理音频播放示例3&#xff1a;场景加载流程示例4&#xff1a;UI管理器示例5&#xff1a;网络服务通信 在Unity中使用外观模式&#xff08;Facade&#xff09;时&#xff0c;主要目的是为了简化复杂子…

Webpack5 基本使用 - 1

Webpack 是什么 webpack 的核心目的是打包&#xff0c;即把源代码一个一个的 js 文件&#xff0c;打包汇总为一个总文件 bundle.js。 基本配置包括mode指定打包模式&#xff0c;entry指定打包入口&#xff0c;output指定打包输出目录。 另外&#xff0c;由于 webpack默认只能打…

6轴机器人运动正解-逆解控制【1】——三种控制位姿的方式

概览&#xff1a; 通过运动学正解控制机器人运动通过运动学逆解控制机器人运动一个简单的物体搬运&#xff08;沿轨迹运动&#xff09; 后续会陆续更新&#xff08;本例仅供学习交流用&#xff09; 一、6轴机器人 二、运动正解控制 通过修改各个轴的角度&#xff0c;实现末…

宠物互联网医院系统:数字化呵护你爱宠的新时代

宠物互联网医院系统正在为宠物主人提供一种前所未有的数字化健康护理体验。通过结合创新技术&#xff0c;这一系统旨在让宠物医疗变得更加便捷、智能和个性化。让我们深入探讨宠物互联网医院系统的技术核心&#xff0c;以及如何应用代码为你的爱宠提供最佳关怀。 1. 远程医疗…

linux clickhouse 安装

1、官网下载clickhouse安装包 下载地址&#xff0c; clickhouse分lts和stable版本&#xff0c;lts是长期版本&#xff0c;一般选择安装lts版本。 其中clickhouse-server是clickhouse服务&#xff0c;就是用来访问数据存储数据&#xff0c;clickhouse-client是用来通过命令访问数…

Java中的HTTPS通信

在Java中实现HTTPS通信&#xff0c;主要涉及到SSL/TLS协议的使用&#xff0c;用于提供数据传输的安全性。下面我们将深入探讨如何使用Java进行HTTPS通信。 一、基本概念 HTTPS&#xff0c;全称为Hypertext Transfer Protocol Secure&#xff0c;是HTTP的安全版本。它使用SSL/…

CSS之高度塌陷和外边距塌陷

目录 1.高度塌陷&#xff08;原因&#xff0c;如何解决&#xff09; 【概念介绍】 【解决办法】 【概念介绍-BFC】 【拓展-BFC的触发条件】 2.外边距塌陷 &#xff08;原因&#xff0c;如何解决&#xff09; 【概念介绍】 【两种情况】 1.相邻块元素 2.嵌套块元素 【…