React18源码: reconciler执行流程

2024-02-23 02:04

本文主要是介绍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 更新之后

这篇关于React18源码: reconciler执行流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/737185

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

maven 编译构建可以执行的jar包

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」👈,「stormsha的知识库」👈持续学习,不断总结,共同进步,为了踏实,做好当下事儿~ 专栏导航 Python系列: Python面试题合集,剑指大厂Git系列: Git操作技巧GO

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除