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

news2024/12/26 2:26:41

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) {
        // ... 跳过很多代码
        // 符合这个条件,走的是 completeWork
        if ((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.coloradmin.cn/o/1408014.html

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

相关文章

macOS磁盘管理工具Paragon Hard Disk Manager,轻松且安全的改变磁盘分区

Paragon Hard Disk Manager mac版是Macos上一款磁盘管理工具,可以帮助你轻松而且安全的随意改变磁盘分区的大小和各种分区参数,作为mac磁盘分区工具也是游刃有余,同时在找回数据的时候也非常容易,并且不会损坏原来的数据&#xff…

tensorboard画图

安装 pip install tensorboardX还得安装TensorFlow pip install tensorflow使用 导包后往里面放数据就行,第一个参数是名称,第二个是y坐标,第三个是x坐标,通过add_scalar进行参数的添加。 import numpy as np from tensorboar…

GPSR路由算法的MATLAB实现

GPSR基于节点地理位置路由信息,采用贪婪策略和右手准则的结合在邻居节点中选择下一跳节点进行数据转发。节点在进行路由选择时,只需知道自己、邻居和目标节点的地理位置信息,无需维护全局网络的链路状态,这在很大程度上降低了网络…

VM下Unbunt虚拟机上网设置

系列文章目录 VM下Unbunt虚拟机上网设置 VM虚拟机上网设置 系列文章目录一、VM虚拟机上网设置 一、VM虚拟机上网设置 右击VM软件中你需要设置的虚拟机,选择设置 宿主机如果你用的是笔记本外加WIFI连接选择NAT网络模式 进入虚拟机看能否上网 不行的话,进…

全网最详细的Cortex-M0指令集汇总

文章目录 Thumb指令集Coretx-M0汇编语言格式寄存器访问指令:MOVEMOV Rd,#immed8.MOV Rd,RmMOVS Rd,#immed8MOVS Rd,RmMRS Rd,SpecialRegMSR SpecialReg,Rd 存储器访问指令:LOADLDR Rt,[Rn,Rm]LDRH Rt,[Rn,Rm]LDRB Rt,[Rn,Rm]LDR Rt,[Rn,#immed5]LDRH Rt,…

VsCode CMake调试QT QString等变量不显示具体值,调试中查看qt源码 (可视化调试配置Natvis)

遇到的问题 当我们在VsCode使用CMake来调试QT程序时,可能会出现变量是十六进制的地址,而看不到具体的值。例如: 如何解决 这时候需要手动设置一下natvis (资源以上传,可以直接下载) 在.vscode文件下找到…

Go 虚拟环境管理工具 gvm 原理介绍与使用指南

本文谈下我对 Go 版本管理的一些想法。让后,我将介绍一个小工具,gvm。这个话题说起来也很简单,但如果想用的爽,还是要稍微梳理下。 背景介绍 Go 的版本管理,并非包的依赖管理,而且关于如何在不同的 Go 版…

配置dns主从服务器,实现正反向解析

一、安装bind服务 yum install bind -y二、修改主配置文件/etc/named.conf 三、配置数据配置文件/var/named/baidu 四、重启服务,进行测试 systemctl restart named

【进口控制器替代】基于Zynq-7020 FPGA的NI 8槽CompactRIO控制器

667 MHz双核CPU,512 MB DRAM,1 GB存储容量,Zynq-7020 FPGA,更宽工作温度范围,8槽CompactRIO控制器 cRIO-9068是一款坚固耐用的无风扇嵌入式控制器,可用于高级控制和监测应用。这款软件设计控制器搭载FPGA、…

链表的反转方法1--迭代法

// 链表头指针结构 typedef struct header {int length; //存储链表结点个数struct linklist *next;//指针域 } Header;// 链表结点结构 typedef struct linknode {int data;//数据域struct linknode* next;//指针域 } LinkNode; 迭代法1-初级版: //反转链表方式1 …

Web06--JavaScript基础02

1、JS流程控制语句 JS与Java一样&#xff0c;也有三个流程控制语句&#xff1a; 顺序结构 选择结构 循环结构 1.1 选择结构 1.1.1 if结构 <script type"text/javascript">if (条件表达式) {代码块;} else if(条件表达式){代码块;} else {代码块;} </scr…

Nas-FPN(CVPR 2019)原理与代码解析

paper&#xff1a;NAS-FPN: Learning Scalable Feature Pyramid Architecture for Object Detection third-party implementation&#xff1a;https://github.com/open-mmlab/mmdetection/tree/main/configs/nas_fpn 本文的创新点 本文采用神经网络结构搜索&#xff08;Neur…

bash 5.2中文修订4

Compound Commands 复合命令 复合命令是 shell 编程语言的结构。每个构造都以保留字或控制运算符开始&#xff0c;并以相应的保留字或运算符终止。与复合命令关联的任何重定向&#xff08;请参阅 Redirections &#xff09;都适用于该复合命令中的所有命令&#xff0c;除非显式…

高质量简历模板网站,免费、免费、免费

你们在制作简历时&#xff0c;是不是基本只关注两件事&#xff1a;简历模板&#xff0c;还有基本信息的填写。 当你再次坐下来更新你的简历时&#xff0c;可能会发现自己不自觉地选择了那个“看起来最好看的模板”&#xff0c;填写基本信息&#xff0c;却没有深入思考如何使简历…

搜维尔科技:【简报】元宇宙数字人赛道,《莉思菱娜》

个性有些古灵精怪时儿安静时而吵闹&#xff0c;虽然以人类寿命来算已经200多岁但在 吸血鬼中还只是个小毛头&#xff0c;从中学开始喜欢打扮偏爱黑白灰色系的服装喜欢时 尚圈&#xff0c;立志想成为美妆或时尚网红不过目前还是学生&#xff0c;脸上的浅色血迹是纹身 贴纸&#…

Spark读取kafka(流式和批数据)

spark读取kafka&#xff08;批数据处理&#xff09; # 按照偏移量读取kafka数据 from pyspark.sql import SparkSessionss SparkSession.builder.getOrCreate()# spark读取kafka options {# 写kafka配置信息# 指定kafka的连接的broker服务节点信息kafka.bootstrap.servers: n…

《幻兽帕鲁》被指AI缝合,开发过程疑点重重,最后附游戏安装教程

由日本游戏工作室Pocketpair开发的《Palworld / 幻兽帕鲁》毫无疑问成为了2024年的首个巨热游戏&#xff01;上周五&#xff08;2024年1月19日&#xff09;游戏上线抢先体验&#xff0c;仅在3天内销量就已突破400万&#xff01;并于2024年1月21日创下了1291967名同时在线玩家的…

[ACM学习] 树形dp之换根

算法概述 总的来说&#xff1a; 题目描述&#xff1a;一棵树求哪一个节点为根时&#xff0c;XXX最大或最小 分为两步&#xff1a;1. 树形dp 2. 第二次dfs 问题引入 如果暴力就是 O(n^2) &#xff0c; 当从1到2的时候&#xff0c;2及其子树所有的深度都减一&#xff0c;其它…

手把手教你快速掌握连接远程git仓库or赋值远程仓库到本地并上传代码到gitee

1. 先去官网安装Git &#xff0c;这里不多赘述网上教程很多 2.1去gitee注册一个账号&#xff0c;然后去我的新建一个仓库&#xff0c;这里是演示一下新手第一次操作的流程 2.2设置仓库名称完成创建(这里的库名随便输入看自己)&#xff1a; 2.3 打开git bash 配置用户名&#x…

Kubernetes-Taint (污点)和 Toleration(容忍)

目录 一、Taint&#xff08;污点&#xff09; 1.污点的组成 2.污点的设置、查看和去除 3.污点实验&#xff1a; 二、Toleration&#xff08;容忍&#xff09; 1.容忍设置的方案 2.容忍实验&#xff1a; Taint 和 toleration 相互配合&#xff0c;可以用来避免 pod 被分配…