React16源码: React中调度之scheduleWork的源码实现

ReactFiber在并发模式下的运行机制 ReactFiber在并发模式下的运行机制 新一年又开始了,React过去一年悄悄实现了Suspense,带来了React Hooks ,源码也大变换了。因此好有必须重新阅读一下,看它是如何实现这些功能,及未来准备开放的并发渲染功能。 React16早期将原来的diff过程拆分成两个阶段,第一个阶段叫reconcile, 也就是原来diff虚拟DOM的过程。这阶段有大量叫reconcile... 阅读详情

scheduleWork


1 ) 概述

  • 在 ReactDOM.render, setState, forceUpdate 这几个方法最终都调用了 scheduleWork 这个方法

  • 在 scheduleWork 当中,它需要去找到更新对应的 FiberRoot 节点

    • 在使用 ReactDOM.render 的时候,传给 scheduleWork 的就是 FiberRoot 节点
    • 在使用 setState, forceUpdate 的时候,传过去的都是某一个组件对应的 fiber 节点,而不是 FiberRoot
    • 这时候需要找到 FiberRoot 节点
  • 如果符合条件需要重置 stack

    • 这个 stack 里面有一些公共的变量
    • 这些公共变量在后续的调度和更新的过程中都非常的重要
    • 如果符合一定的条件,要重置这个stack
  • 如果符合条件就去请求工作调度

    • 注意,并不是所有情况都符合这个要求,需要进行工作调度
  • 拿之前的一个 Fiber Tree 节点来举例

              
                -----current-----> 
      FiberRoot                     RootFiber
                <---stateNode-----    |   
                                      |
                                    child  
                                      |
                                      ↓
                                      App
                                      |
                                    child
                                      |
                                      ↓
                                      |
                                      div
                                      / \
                                     /   \
                                child   child
                                  /        \
                                 /          \
                                /            \
                              Input-sibling-> List
                                |               \
                              child            child
                                |                 \
                                ↓                  \
                              input               span --sibling--> span --sibling--> span --sibling--> button
    
    • 在上面这个 fiber tree 结构的 demo示例中,点击了最后这个 button 节点
    • 实际调用 setState 是我们写的这个 List 组件
    • 最终是把 RootFiber 这个 fiber 节点加入到调度队列当中
    • 而不是直接把我们的 List 它对应的 fiber 对象加入到调度队列当中
    • 每一次进入到调度队列的, 都是一个 RootFiber 这个对象, 不会是其他的
    • 因为更新的开始,也是从 RootFiber 开始的

2 )源码

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

function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  const root = scheduleWorkToRoot(fiber, expirationTime);
  if (root === null) {
    return;
  }

  if (
    !isWorking &&
    nextRenderExpirationTime !== NoWork &&
    expirationTime < nextRenderExpirationTime
  ) {
    // This is an interruption. (Used for performance tracking.)
    interruptedBy = fiber;
    resetStack();
  }
  markPendingPriorityLevel(root, expirationTime);
  if (
    // If we're in the render phase, we don't need to schedule this root
    // for an update, because we'll do it before we exit...
    !isWorking ||
    isCommitting ||
    // ...unless this is a different root than the one we're rendering.
    nextRoot !== root
  ) {
    const rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    // Reset this back to zero so subsequent updates don't throw.
    nestedUpdateCount = 0;
    invariant(
      false,
      'Maximum update depth exceeded. This can happen when a ' +
        'component repeatedly calls setState inside ' +
        'componentWillUpdate or componentDidUpdate. React limits ' +
        'the number of nested updates to prevent infinite loops.',
    );
  }
}
  • 这个方法的接收的参数 fiber: Fiber, expirationTime: ExpirationTime
    • 第一个是 fiber对象
    • 第二个是 expirationTime,就是在创建更新的时候去计算出来的那个过期时间
  • 它一进来就调用了一个方法叫做 scheduleWorkToRoot
    function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
      recordScheduleUpdate();
    
      if (__DEV__) {
        if (fiber.tag === ClassComponent) {
          const instance = fiber.stateNode;
          warnAboutInvalidUpdates(instance);
        }
      }
    
      // Update the source fiber's expiration time
      if (
        fiber.expirationTime === NoWork ||
        fiber.expirationTime > expirationTime
      ) {
        fiber.expirationTime = expirationTime;
      }
      let alternate = fiber.alternate;
      if (
        alternate !== null &&
        (alternate.expirationTime === NoWork ||
          alternate.expirationTime > expirationTime)
      ) {
        alternate.expirationTime = expirationTime;
      }
      // Walk the parent path to the root and update the child expiration time.
      let node = fiber.return;
      let root = null;
      if (node === null && fiber.tag === HostRoot) {
        root = fiber.stateNode;
      } else {
        while (node !== null) {
          alternate = node.alternate;
          if (
            node.childExpirationTime === NoWork ||
            node.childExpirationTime > expirationTime
          ) {
            node.childExpirationTime = expirationTime;
            if (
              alternate !== null &&
              (alternate.childExpirationTime === NoWork ||
                alternate.childExpirationTime > expirationTime)
            ) {
              alternate.childExpirationTime = expirationTime;
            }
          } else if (
            alternate !== null &&
            (alternate.childExpirationTime === NoWork ||
              alternate.childExpirationTime > expirationTime)
          ) {
            alternate.childExpirationTime = expirationTime;
          }
          if (node.return === null && node.tag === HostRoot) {
            root = node.stateNode;
            break;
          }
          node = node.return;
        }
      }
    
      if (root === null) {
        if (__DEV__ && fiber.tag === ClassComponent) {
          warnAboutUpdateOnUnmounted(fiber);
        }
        return null;
      }
    
      if (enableSchedulerTracing) {
        const interactions = __interactionsRef.current;
        if (interactions.size > 0) {
          const pendingInteractionMap = root.pendingInteractionMap;
          const pendingInteractions = pendingInteractionMap.get(expirationTime);
          if (pendingInteractions != null) {
            interactions.forEach(interaction => {
              if (!pendingInteractions.has(interaction)) {
                // Update the pending async work count for previously unscheduled interaction.
                interaction.__count++;
              }
    
              pendingInteractions.add(interaction);
            });
          } else {
            pendingInteractionMap.set(expirationTime, new Set(interactions));
    
            // Update the pending async work count for the current interactions.
            interactions.forEach(interaction => {
              interaction.__count++;
            });
          }
    
          const subscriber = __subscriberRef.current;
          if (subscriber !== null) {
            const threadID = computeThreadID(
              expirationTime,
              root.interactionThreadID,
            );
            subscriber.onWorkScheduled(interactions, threadID);
          }
        }
      }
    
      return root;
    }
    
    • 上面首先调用了 recordScheduleUpdate 这个方法是react当中的用来记录更新流程当中的时间
    • 这里涉及的东西比较多,先跳过,涉及一个独立的模块
    • DEV 的判断也跳过
    • 在这个方法中
      • 根据传入进来的 fiber 节点,它要去向上寻找,找到对应的 RootFiber 对象
      • 找的过程当中,进行一系列的操作
      • if (fiber.expirationTime === NoWork || fiber.expirationTime > expirationTime)
        • 当这个节点没有任何更新(没有设置过过期时间) 或者 已有更新产生未完成的任务的优先级 低于 当前的更新
        • 则将本身设置成优先级更高的 expirationTime
        • 这里主要任务是更新优先级
      • 第二步获取 alternate 并进行判断 if (alternate !== null && (alternate.expirationTime === NoWork || alternate.expirationTime > expirationTime))
        • 这很明显就是用来更新 alternate 的 expirationTime
        • 它其实跟上面更新 fiber.expirationTime 是一样的
      • 接下去,往上一层去找,比如 List 这个组件的 return 属性是 上一层的 div 组件
      • 进入判断 if (node === null && fiber.tag === HostRoot)
        • 注意,只有 RootFiber.return === null, 其他的都有上级Fiber
        • 当 node === null 时,当前 fiber 就是 RootFiber, 同时,HostRoot 代表的就是 RootFiber
        • 这时候,root = fiber.stateNode
      • 如果不满足上述判断,则进入循环处理
        • while循环,向上查找
          • node.childExpirationTime 是否是子树中优先级最高的
          • 如果不是高优先级,则更新成高优先级的
          • 同样对 alternate.childExpirationTime 的判断也是同样的更新
          • 接着,对 node.return 和 node.tag 的判断是否是 RootFiber, 赋值 root
          • 如果不是,则 node = node.return 向上查找
      • 如果 root === null 的时候,则 return null
      • 下面的 if (enableSchedulerracing) 不涉及主流程,跳过
      • 最终 return root, 把 FiberRoot 返回
  • 获取 root 后,如果 root 是 null,则 return
  • 接着 if(!isWorking && nextRenderExpiration !== NoWork && expirationTime < nextRenderExpirationTime)
    • 如果没有在渲染更新的任务,并且,任务是异步任务没有执行完,并且新任务的优先级高于nextRenderExpirationTime 优先级
    • 这个意思是 新的高优先级任务打断了老的低优先级的任务,这是一个非常重要的特性
    • 它让我们可以优先执行高优先级的任务
    • interruptedBy 是一个被用来记录在哪里打断的变量值,这里可以不用太关注
    • 进入 resetStack()
      function resetStack() {
        // nextUnitOfWork 用来记录遍历整个子树的时候,执行到了哪个节点的更新, 即下一个即将要更新的节点
        // 这个判断如果是 true 则代表,之前的更新的是一个异步的任务,执行到一部分,由于时间片不够,把执行权交给浏览器
        // 这个值记录了下一个要执行的节点,如果现在没有优先级任务中断,它会回来继续执行 nextUnitOfWork 的任务更新
        if (nextUnitOfWork !== null) {
          // 指向上级,向上查找
          let interruptedWork = nextUnitOfWork.return;
          // 不断向上找被打断的任务,执行 unwindInterruptedWork
          while (interruptedWork !== null) {
            // 有了更高优先级的任务进来了,要进行更新,还是要从头开始更新,有存在的 nextUnitOfWork
            // 说明上层的几个组件 可能已经进行过更新了, 新的任务进来,再从头进行更新可能会导致前后的state并非这次想要更新的state
            // 会导致 state 错乱,在这里有 nextUnitOfWork 的情况,要把之前已经更新过的节点(上一个优先级任务对应的节点) 进行状态退回
            // 把状态退回到没有更新过的状态,再去执行优先级更高的任务,这块具体的先跳过
            unwindInterruptedWork(interruptedWork);
            interruptedWork = interruptedWork.return;
          }
        }
      
        if (__DEV__) {
          ReactStrictModeWarnings.discardPendingWarnings();
          checkThatStackIsEmpty();
        }
        // 设置公共变量的重置
        nextRoot = null;
        nextRenderExpirationTime = NoWork;
        nextLatestAbsoluteTimeoutMs = -1;
        nextRenderDidError = false;
        nextUnitOfWork = null;
      }
      
      
  • 接着,markPendingPriorityLevel
    • 这部分先跳过,涉及流程较多
  • 接着进入判断,if(!isWorking || isCommitting || nextRoot !== root) 是否要 requestWork
    • 当没有正在渲染更新,或正在提交,或不是 本root 节点(注:是针对多应用而言的,单应用只有一个root,这里很少会匹配)
      • 注意,isWorking 会包含 committing
      • committing 是第二阶段,是不可打断的阶段,把fiber树渲染完成后,更新到dom上的这个过程是 commiting 阶段
      • isCommitting 只有在中间执行的过程中才会是 true, 其他才会是false, 可以查看 commitRoot 函数
    • 符合条件,赋值 rootExpirationTime 并调用 requestWork 函数
      • 为何要重新读取 expirationTime, 因为之前调用过 markPendingPriorityLevel 可能导致
      • root.epirationTime 不一定和传入的 expirationTime 是一样的
  • 最后是涉及到 nestedUpdateCount 的判断
    • 用来防止在组件更新的流程中,比如在 componentDidMount 中再次修改状态
    • 导致又进行了一次更新, 导致无限循环的处理,这里进行提醒
React-从源码分析React Fiber工作原理 本文的demo仓库在https://github.com/qiqingjin/blog/tree/master/React_Redux,喜欢请star哟~ 为什么要重写React React16 以前 React16 以前,对virtural dom的更新和渲染是同步的。就是当一次更新或者一次加载开始以后,diff virtual dom并且渲染的过程是一口气完成的。如果组件... 阅读详情

相关推荐

Windows 2012 SYSVOL复制失败的处理

题外话:晓原理、知架构、查日志是排错的三件法宝今天在DC1上新建并链接了一个组策略对像,在客户端刷新时,却报下面的错误:“处理组策略失败。Windows无法应用组策略对象LDAP….”如下图所示:于是在客户端用\\domain.com访问,发现\\domain.com\SYSVOL\domain.com\policies下面没有上面报错的组策略对象。这是怎么一回事呢,pin...

weixin_33989780的博客 1669

react事件系统(老版本)

react事件系统

lin_fightin的博客 885

手把手教你驱动6脚三位一体数码管:从引脚分析到C语言扫描代码实战

本文详细解析了如何逆向工程并驱动6脚三位一体数码管的全过程,从引脚分析到C语言扫描代码实战。通过系统化的测试和优化,作者总结出一套高效的驱动架构和调试方法,适用于工业设备中的非标准数码管应用。

weixin_42632434的博客 213

React16源码: React中的schedule调度整体流程

React Fiber Scheduler 是 react16 最核心的一部分,这块在这个包中这个包的核心是 fiber reconciler,也即是 fiber 结构fiber的结构帮助我们把react整个树的应用,更新的流程,能够拆成每一个fibrer对象为单元的一个更新的流程这种单元的形式把更新拆分出来之后,给每个不同的任务提供一个优先级,以及我们在更新的过程当中,可以中断因为我们记录更新到了哪一个单元,中断了之后,可以过一会儿再回过头来,继续从这个单元开始,继续之前没有做完的更新。

Wang的专栏 1604

Windows-Anaconda3+TensorFlow安装教程

测试环境:win10-64位和win7-64位 (系统必须是64位,因为TensorFlow目前只支持64位) 一、安装Anaconda3 1.下载Anaconda3 Anaconda官网链接:link 这里我们选择Windows版本,python3.6-version-64位,如下图所示。 2.下载完成之后,点击安装Anaconda3 注意:安装过程中要选择添加环境变量,如图 待安装完成...

DoctorCuiLab的博客 3089

Python | 处理CMIP6 NC数据,以tas为例

各位同学,一般在文章最后一章我们可能会做不同气候情景下变量的未来时空变化特征,类似上图。从cmip6下载的nc数据常需要转换格式、裁剪到研究区和重采样等,本期代码可以解决这类问题。

qq_48539301的博客 1384

React 事件系统流程 原理理解

文章目录事件系统事件处理事件合成事件绑定事件触发总结 事件系统 问:React 为什么要写出一套自己的事件系统呢? 答:1、首先,对于不同的浏览器,对事件存在不同的兼容性,React 想实现一个兼容全浏览器的框架, 为了实现这个目标就需要创建一个兼容全浏览器的事件系统。 2、其次,v17 之前 React 事件都是绑定在 document 上,v17 之后 React 把事件绑定在应用对应的 容器 container 上,将事件绑定在同一容器统一管理,防止很多事件直接绑定在原生的 DOM 元素上,造成

YuLong的博客 1247

【Claude】Invalid API key 无效密钥报错已解决

API 拒绝了你提供的 key。最高频的根因是老的/被撤销的 key 藏在某处自动加载的配置文件里,而你对此不知情。→ 检查所有可能注入 key 的来源(.env.envrc、shell 配置、IDE 设置、)→ 清除无效来源 → 用/status验证。参考:Claude Code 官方《错误参考》"身份验证"章节、社区 Invalid API key 排查记录。

博客 7925

react源码解析——react 任务调度:scheduleWork

react 任务调度

weixin_47338877的博客 640

基于 Qt C++ 开发一套西门子量子测量系统的控制与数据采集平台

sensorIdCombo->addItems({"QTS-100-001 (温度)", "QPG-200-001 (压力)", "QDS-300-001 (位移)"});precisionTable->setHorizontalHeaderLabels({"传感器ID", "类型", "标称精度", "实际精度", "校准状态"});QWidget *dispWidget = createSensorWidget("量子位移", "mm (±1μm)", 50.0, 0.001);// 传感器状态更新。

寒寒豆豆软件公司的博客 255

低光照图像增强:从传统方法到深度学习

低光照图像增强:从传统方法到深度学习​ 人工智能,计算机视觉,大模型,AI,本文聚焦视觉模型的偏差与公平性问题,阐述了偏差在性别、种族、年龄等方面的表现及对医疗、司法、安防等领域的影响。分析了偏差源于数据采集与标注问题及算法局限。介绍了准确率差异等公平性评估指标、相关数据集和工具,还给出数据处理、算法改进、模型训练等避免歧视的策略,并附代码示例与实际案例,为提升视觉模型公平性提供多方面参考。

不踩坑,不知道坑有多深;不总结,踩过的坑白踩。 2万+

react的state和useState你了解多少?带你深入react state useState

react的state和useState你了解多少?带你深入react state useState

weixin_51472145的博客 1448

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

基于React 源码深入浅出setState:深度刨析updater的结构和原理

作者 : 墨成 React 版本 :16.4.1 阅读本文之前,建议阅读: 1.基于React 源码深入浅出setState:官方文档的启示录 2.基于React 源码深入浅出setState:setState异步实现 在上一篇详细了解了setState的一些机制和原理,同时对 updater也作了简单的解释,这篇文章我们将详细了解这个updater的数据结构和调用堆栈. 代码如...

阳关专栏 684

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

【笔记-react】《imooc-React源码深度解析 高级前端工程师必备技能》

第01章 课程导学 01-01 课程导学 一、UI=fn(x) 核心api:setState 没有directive双向绑定 二、vue,angular更新大版本,需要调整兼容 三、react,引入fiber,从根本上解决了,js单线程运行,如果计算量太大,导致动画卡帧,交互卡顿问题 四、第1章:React API,及每个API的作用 createElement、createContext、JSX=>JS、ConcurrentMode、Ref、Component、Suspense、Hooks 五、第2

aSuncat 1167

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

「译」React Fiber 那些事: 深入解析新的协调算法

翻译自:Inside Fiber: in-depth overview of the new reconciliation algorithm in React React 是一个用于构建用户交互界面的 JavaScript 库,其核心 机制 就是跟踪组件的状态变化,并将更新的状态映射到到新的界面。在 React 中,我们将此过程称之为...

weixin_34362991的博客 288

【React源码】(七)React 调度原理(scheduler)

本节主要分析了scheduler包中调度原理, 也就是React两大工作循环中的任务调度循环. 并介绍了时间切片和可中断渲染等特性在任务调度循环中的实现.scheduler包是React运行时的心脏, 为了提升调度性能, 注册task之前, 在包中做了节流和防抖等措施.

weixin_44828588的博客 1741

react合成事件

react合成事件 概念 react合成事件是react模拟原生dom事件所有能力得一个事件对象。拥有与浏览器原生事件相同的接口。 本篇文章基于V17.0.3来研究react的合成事件 都知道react16是把事件绑定在document上,自己实现了一套事件机制,react17的区别是把事件绑定到根节点 起源 从react将jsx解析成element的格式。带有事件的情况会解析成如下所示: props解析携带onClick {$$typeof: Symbol(react.element), type: 'd

luo_qianyu的博客 2996

react底层原理解析之fiber

Fiber的特点/作用 Fiber能够使得动画、布局和页面交互变得更加的流畅。 一:Fiber的概念   React Fiber是react执行渲染时的一种新的调度策略,JavaScript是单线程的,一旦组件开始更新,主线程就一直被React控制,这个时候如果再次执行交互操作,就会卡顿。   React Fiber就是通过对象记录组件上需要做或者已经完成的更新,一个组件可以对应多个Fiber。 ...

吴小迪的博客 4万+

React之状态(State)

在React当中,当你更新组件的state,然后新的state就会重新渲染到页面中。在这个时候不需要你操作任何DOM。你也可以认为组件在React当中是一个状态机(State Machines)。当用户进行操作时会实现不同的状态,然后再渲染到你的页面中,让你的页面与数据始终保持一致。 如果对状态不好理解的朋友,你可以认为状态即是数据! 现在我们先来通过ES6类React.Compo...

张培跃的个人博客 1773

[译] 深入解析React中state和props更新

原文链接:medium.com/react-in-de… 到react fiber内部一探究竟 在我之前文章Fiber内部:深度概述React新协调算法中,我铺设了基础内容,用于理解我这篇文章讲解的更新处理的技术细节。 我已经概述过将在这篇文章中用到的主要数据结构和概念,特别是Fiber节点、当前和工作过程树、副作用和作用列表,我也对主要的算法提供过大致的说明,且解释过**render和comm...

weixin_34268753的博客 1201

react中的state状态管理(个人流程理解)---小白篇

前言:对于react的状态管理这一部分,尤其是在融合了项目里面后,各个流程步骤有点拎不清。特以一个小点入手,理解记录一下。会有理解不当之处,请指正。小白式理解,欢迎大神赐教。 背景:该用户管理页面分为左右两个部分,左侧采用antd的树组件实现对区域的展示,右侧为点击相应的树节点后展示的用户列表,点击操作可以对用户进行相应的编辑和删除。以删除为例。 删除其实是一个很简单的操作,一般记住id之后调...

taylorzun的博客 5580

react+nodejs前后端学习笔记

参考别人的简单笔记应用demo思考:https://www.cnblogs.com/tianheila/p/5176556.html思想:1.前端写页面动作逻辑,请求数据采用ajax请求回调。ajax请求形式:$.ajax({ url:"url", tyoe:"post", dataType:"json", data:JSON.stringify(),//反序列化 c...

qq_32720539的博客 2007

React基础原理(state数据,setState方法,获取页面Dom节点,事件绑定,生命周期)

React基础原理(state数据,setState方法,获取页面Dom节点,事件绑定,生命周期) React组件内部state数据 当state中的数据发送变化以后,会调用render方法重新渲染VDOM。 state相当于重写,因为在react中,就有这个 setState方法: 为了解决多次设值state产生的多次渲染,会使用节流方法多次设值进行合并处理,相当于vue中的this.$nextTick()方法 如果需要得到重新赋值后的值,应该在回调中去拿 经典使用场景:第二次ajax请求,依赖第一次aj

丢失的Litte_志伟的博客 1299

Fix signatures do not match the previously installed version

报错信息FAILURE: Build failed with an exception.* What went wrong: Execution failed for task ':app:installDebug'. > com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException: Fai

Wang的专栏 2万+
上一篇: React16源码: React中的schedule调度整体流程
下一篇: React16源码: React中调度之requestWork的源码实现
Wang's Blog
博客等级 码龄12年 7434粉丝 2421原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值