React18源码: reconciler执行流程

reconciler执行流程


1 )概述

  • 此处先归纳一下react-reconciler包的主要作用,将主要功能分为4个方面:
    • 输入:暴露api函数(如:scheduleUpdateOnFiber), 供给其他包(如react包)调用
    • 注册调度任务:与调度中心(scheduler包)交互,注册调度任务task,等待任务回调
    • 执行任务回调:在内存中构造出fiber树,同时与渲染器(react-dom)交互,在内存中创建出与fiber对应的DOM节点
    • 输出:与渲染器(react-dom)交互,渲染DOM节点
  • 图中的1,2,3,4步骤可以反映react-reconciler包从输入到输出的运作流程
  • 这是一个固定流程,每一次更新都会运行

2 )输入

  • 在ReactFiberWorkLoop.js中,承接输入的函数只有scheduleUpdateOnFiber

  • 在 react-reconciler 对外暴露的api函数中,只要涉及到需要改变fiber的操作(无论是首次渲染或后续更新操作)

  • 最后都会间接调用 scheduleUpdateOnFiber

  • 所以scheduleUpdateOnFiber函数是输入链路中的必经之路

    //唯一接收输入信号的函数
    export function scheduleUpdateOnFiber(
      fiber: Fiber,
      lane: Lane,
      eventTime: number,
    ) {
      // ... 省略部分无关代码
      const root = markUpdateLaneFromFiberToRoot(fiber, lane);
      // 同步
      if (lane === SyncLane) {
        if (
          (executionContext & LegacyUnbatchedContext) !== NoContext &&
          (executionContext & (RenderContext | CommitContext)) === NoContext
        ) {
          // 直接进行fiber构造
          performSyncWorkOnRoot(root);
        } else {
          // 注册调度任务,经过`Scheduler'包的调度,间接进行`fiber构造'
          ensureRootIsScheduled(root, eventTime);
        }
      } else {
        // 注册调度任务,经过`Scheduler`包的调度,间接进行`fiber构造`
        ensureRootIsScheduled(root, eventTime);
      }
    }
    
  • 逻辑进入到scheduleUpdateOnFiber之后,后面有2种可能:

    • 1.不经过调度,直接进行fiber构造.
    • 2.注册调度任务,经过Scheduler包的调度,间接进行fiber构造.

2 )注册调度任务

与输入环节紧密相连,scheduleUpdateOnFiber函数之后,立即进入 ensureRootIsScheduled 函数

// ... 省略部分无关代码
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  //前半部分:判断是否需要注册新的调度
  const existingCallbackNode - root. callbackNode;
  const nextlanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  const newCallbackPriority = returnNextLanesPriority();
  if (nextLanes === NoLanes) {
    return;
  }
  if (existingCallbackNode !== null) {
    const existingCallbackPriority = root.callbackPriority;
    if (existingCallbackPriority === newCallbackPriority) {
      return;
    }
    cancelCallback(existingCallbackNode);
  }
  // 后半部分:注册调度任务
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority){
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}
  • ensureRootIsScheduled的逻辑很清晰,分为2部分:
    • 1.前半部分:判断是否需要注册新的调度(如果无需新的调度,会退出函数)
    • 2.后半部分:注册调度任务
      • performSyncWorkOnRoot 或 performConcurrentWorkOnRoot 被封装到了任务回调 (schedulecallback)
      • 等待调度中心执行任务,任务运行其实就是执行 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot

3 )执行任务回调

  • 任务回调,实陈上就是执行 performSyncWorkOnRoot 或 performConcurrentWorkOnRoot

  • 简单看一下它们的源码将主要逻辑剥离出来,单个函数的代码量并不多

    //..,省略部分无关代码
    function performSyncWorkOnRoot(root) {
      let lanes;
      let exitStatus;
    
      lanes = getNextLanes(root, NoLanes);
      // 1. fiber树构造
      exitStatus = renderRootSync(root, lanes);
    
      // 2. 异常处理:有可能fiber构造过程中出现异常
      if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
        // ...
      }
    
      // 3. 输出:渲染fiber树
      const finishedWork: Fiber = (root.current.alternate: any);
      root.finishedwork = finishedWork;
      root.finishedLanes = lanes;
      commitRoot(root);
    
      // 退出前再次检测,是否还有其他更新,是否需要发起新调度
      ensureRootIsScheduled(root, now());
      return null;
    }
    
  • performSyncWorkOnRoot 的逻辑很清晰,分为3部分:

    • fiber 树构造

    • 异常处理: 有可能fiber构造过程中出现异常

    • 调用输出

      // ... 省略部分无关代码
      function performConcurrentWorkOnRoot(root) {
        const originalCallbackNode = root.callbackNode;
      
        // 1、刷新pending状态的effects,有可能某些effect会取消本次任务
        const didFlushPassiveEffects = flushPassiveEffects();
        if (didFlushPassiveEffects) {
          if (root.callbackNode !== originalCallbackNode) {
            // 任务被取消,退出调用
            return null;
          } else {
            // Current task was not canceled. Continue.
          }
        }
      
        // 2.获取本次渲染的优先级
        let lanes = getNextLanes(
          root,
          root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
        );
      
        // 3.构造fiber树
        let exitStatus = renderRootConcurrent(root, lanes);
        if (
          includesSomeLane(
            workInProgressRootIncludedLanes,
            workInProgressRootUpdatedLanes,
          )
        ) {
          // 如果在render过程中产生了新的update,且新update的优先级与最初render的优先级有交集
          // 那么最初render无效,丢弃最初render的结果,等待下一次调度
          prepareFreshStack(root, NoLanes);
        } else if (exitStatus !== RootIncomplete) {
          // 4、异常处理:有可能fiber构造过程中出现异常
          if (exitStatus == RootErrored) {
            // ...
          }
          const finishedWork: Fiber = (root.current. alternate: any);
          root.finishedWork = finishedwork;
          root.finishedLanes = lanes;
          // 5.输出:渲染fiber树
          finishConcurrentRender(root, exitStatus, lanes);
        }
      
        // 退出前再次检测,是否还有其他更新,是否需要发起新调度
        ensureRootIsScheduled(root, now());
        if (root.callbackNode === originalCallbackNode) {
          // 渲染被阻断,返回一个新的performConcurrentWorkOnRoot函数。等待下一次调用
          return performConcurrentWorkOnRoot.bind(null, root);
        }
        return null;
      }
      
  • performConcurrentWorkOnRoot 的逻辑与 performSyncWorkOnRoot 的不同之处在于

  • 对于可中断渲染的支持:

    • 1.调用 performConcurrentWorkOnRoot 函数时,首先检查是否处于 render 过程中,是否需要恢复上一次渲染
    • 2.如果本次渲染被中断,最后返回一个新的 performConcurrentWorkOnRoot 函数,等待下一次调用

4 )输出

// ... 省略部分无关代码
function commitRootImpl(root, renderPriorityLevel) {
  // 设置局部变量
  const finishedWork = root.finishedWork;
  const lanes - root. finishedLanes;

  // 清空FiberRoot对象上的属性
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  root.callbackNode = null;
  
  // 提交阶段
  let firstEffect = finishedWork.firstEffect;
  if (firstEffect !== null) {
    const prevExecutionContext - executionContext;
    executionContext |= CommitContext;
    // 阶段1:dom突变之前
    nextEffect = firstEffect;
    do {
      commitBeforeMutationEffects();
    } while (nextEffect !== null);

    // 阶段2:dom突变,界面发生改变
    nextEffect = firstEffect;
    do {
      commitMutationEffects(root, renderPriorityLevel);
    } while (nextEffect !== null);
    root.current = finishedWork;
    
    // 阶段3:layout阶段,调用生命周期componentDidUpdate和回调函数等
    nextEffect = firstEffect;
    do{
      commitLayoutEffects(root, lanes);
    } while (nextEffect !== null);
    nextEffect = null;
    executionContext = prevExecutionContext;
  }
  ensureRootIsScheduled(root, now());
  return null;
}
  • 在输出阶段,commitRoot 的实现逻辑是在 commitRootImpl 函数中
  • 其主要逻辑是处理副作用队列,将最新的fiber树结构反映到DOM上
  • 核心逻辑分为3个步骤:
    • 1.commitBeforeMutationEffects
      • dom变更之前,主要处理副作用队列中带有Snapshot, Passive标记的fiber节点
    • 2.commitMutationEffects
      • dom变更,界面得到更新.主要处理副作用队列中带有
      • Placement,Update,Deletion, Hydrating标记的fiber节点
    • 3.commitLayoutEffects
      • dom变更后,主要处理副作用队列中带有 update | Callback 标记的fiber节点.
  • 这块流程参考 React16版本的流程,看下不同之处
    • 参考: https://blog.csdn.net/Tyro_java/article/details/135845906
  • 所以,整个 reconciler 的执行过程中,核心做了2个事情
    • 1 )Render (基于task, 可以被打断, 可以被打断的前提是基于渲染 mode)
      • 初始化 fiber
      • 更新 fiber
    • 2 )commit
      • dom 变更之前
      • dom 变更
      • dom 更新之后

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

mysql mgr集群部署

一、前言 mysql mgr集群是为了实现mysql高可用,分为单主集群和多主集群,单主集群只有一个主节点可写,节点发生故障时,自动进行主从的故障切换,多主集群所有节点都可写,当节点发生故障时,将故障节…

activeMq将mqtt发布订阅转成消息队列

1、activemq.xml置文件新增如下内容 2、mqttx测试发送: 主题(配置的模糊匹配,为了并发):VirtualTopic/device/sendData/12312 3、mqtt接收的结果 4、程序处理 package comimport cn.hutool.core.date.DateUtil; imp…

python专业版破解激活(超详细)

python专业版破解激活 1.下载pycharm应用程序 这里我使用的版本是pycharm-professional-2023.3.2 下载pycharm程序的连接为: 百度网盘 请输入提取码 提取码为:nym0 2.安装 选择安装路径 下一步 这里全选 下一步 这里直接点击安装就可,其…

探索分布式强一致性奥秘:Paxos共识算法的精妙之旅

提到分布式算法,就不得不提 Paxos 算法,在过去几十年里,它基本上是分布式共识的代名词,因为当前一批常用的共识算法都是基于它改进的。比如,Fast Paxos 算法、Cheap Paxos、Raft 算法等。 由莱斯利兰伯特(L…

R语言数据分析(五)

R语言数据分析(五) 文章目录 R语言数据分析(五)前言一、什么是整洁的数据二、延长数据2.1 列名中的数据值2.2 pivot_longer()的处理原理2.3 列名中包含许多变量的情况2.4 列名同时包含数据和变量 三、扩宽数据3.1 pivot_wider的处…

Electron实战之环境搭建

工欲善其事必先利其器,在进行实战开发的时候,我们最终的步骤是搞好一个舒服的开发环境,目前支持 Vue 的 Electron 工程化工具主要有 electron-vue、Vue CLI Plugin Electron Builder、electron-vite。 接下来我们将分别介绍基于 Vue CLI Plu…

查询数据库的编码集Oracle,MySQL

1、查询数据库的编码集Oracle,MySQL 1.1、oracle select * from v$nls_parameters where parameterNLS_CHARACTERSET; 查询版本:SELECT * FROM v$version 2、MySQL编码集 SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SC…

隐私也要付费?Meta公司为收集用户数据再出“奇招”

Cybernews网站消息,有相关人士表示,如果欧洲数据保护委员会(EDPB)不明确指出Meta公司的“付费或同意”的模式违反了欧盟的隐私法规,那么这一模式很可能会被大规模复制,危及数百万欧洲公民的自由选择权。 自…

Jenkins2.426邮件通知配置

之前安装的jenkins出现问题了,重新装了jenkins,需要重新配置:Maven,JDK,Allure报告,邮件通知,Extended E-mail Notification等 配置Maven,JDK参考:CICD集合(四):Jenkins…

排序第三篇 直接插入排序

插入排序的基本思想是: 每次将一个待排序的记录按其关键字的大小插入到前面已排好序的文件中的适当位置, 直到全部记录插入完为止。 一 简介 插入排序可分为2类 本文介绍 直接插入排序 它的基本操作是: 假设待排充序的记录存储在数组 R[1……

2.22 Qt day3 多界面跳转+qss登录界面优化+发布软件+对话框

思维导图: 完善对话框,点击登录对话框,如果账号和密码匹配,则弹出信息对话框,给出提示”登录成功“,提供一个Ok按钮,用户点击Ok后,关闭登录界面,跳转到其他界面 如果账号…

我们在SqlSugar开发框架中,用到的一些设计模式

我们在《SqlSugar开发框架》中,有时候都会根据一些需要引入一些设计模式,主要的目的是为了解决问题提供便利和代码重用等目的。而不是为用而用,我们的目的是解决问题,并在一定的场景下以水到渠成的方式处理。不过引入任何的设计模…

【教3妹学编程-算法题】匹配模式数组的子数组数目 I

3妹:2哥2哥,你有没有看到上海女老师出轨男学生的瓜啊。 2哥 : 看到 了,真的是太毁三观了! 3妹:是啊, 老师本是教书育人的职业,明确规定不能和学生谈恋爱啊,更何况是出轨。 2哥 : 是啊…

HarmonyOS—LocalStorage:页面级UI状态存储

LocalStorage是页面级的UI状态存储,通过Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage也可以在UIAbility实例内,在页面间共享状态。 本文仅介绍LocalStorage使用场景和相关的装饰器:LocalStorageProp和LocalS…

大保司保费贵,是否物有所值?

《大保司保费贵,是否物有所值》 这是罗师兄的原创文章 预计8-9分钟读完 作者:罗师兄 微信号:luoyun515 当我们想要买一份重疾险、储蓄险等长期险时, 我们会发现,同样的保障责任和保额, 不同保险公司的…

mac苹果电脑系统最好用的清理软件CleanMyMac2024功能介绍及如何激活解锁许可证

CleanMyMac X的界面设计简洁大气,为用户提供了直观且易于操作的使用体验。 布局清晰:界面布局非常明朗,左侧是功能栏,右侧则是信息界面。这种布局方式使得用户能够迅速找到所需的功能选项,提高了操作效率。色彩搭配&a…

Flutter常用命令,持续更新

目录 前言 Flutter 常用命令 Dart 常用命令 adb 常用命令(用于 Android 开发) 前言 当在开发Flutter项目时,熟悉一些常用的命令是非常重要的。这些命令可以帮助你执行各种任务,从构建应用程序到调试和测试。以下是一些Flutte…

亿道丨三防平板丨加固平板丨三防加固平板丨改善资产管理

库存资产管理中最重要的部分之一是准确性;过时的库存管理技术会增加运输过程中人为错误、物品丢失或纸张损坏的风险。如今随着三防平板电脑的广泛使用,库存管理也迎来了好帮手,通过使用三防平板电脑能够确保库存管理、数据存储和记录保存的准…

Hive【内部表、外部表、临时表、分区表、分桶表】【总结】

目录 Hive的物种表结构特性 一、内部表 建表 使用场景 二、外部表 建表:关键词【EXTERNAL】 场景: 外部表与内部表可互相转换 三、临时表 建表 临时表横向对比​编辑 四、分区表 建表:关键字【PARTITIONED BY】 场景: 五、分桶表 …

pip安装依赖环境出现的问题

一、error: subprocess-exited-with-error! 1、前期一直百度的错误如标题所示,得到的方案如下:(但没解决问题) (1)升级setuptools库,或者降低固定版本 //升级setuptools库,或者降低…