React16源码: React中commit阶段的commitRoot的主流程源码实现

本文主要是介绍React16源码: React中commit阶段的commitRoot的主流程源码实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

commitRoot


1 )概述

  • 在react中有 render 阶段 和 commit 阶段,这是两个不同的阶段
  • 1 )之前的渲染更新都是render阶段
  • 在render阶段,会经历一系列的调度,一系列的节点的更新过程
  • 需要去重新计算它的 state, props 生成新的fiber树和dom树
  • 在这个过程中,每一个节点的更新过程,它都是独立的
  • 每一个节点更新完之后,它都可以跳出这个更新的循环
  • 然后根据不同的更新模式可以被中断以及可以分片的更新
  • 能够让 react 把更多的优先级交还给浏览器
  • 让它可以及时的更新用户反馈,处理一些持续性的动画,让整体的页面会变得更流畅
  • 2 )进入到commit阶段,整个流程是不可以被中断的
  • react 尽量把更少的任务交给 commit 阶段
  • 所以,commit 阶段需要去更新的节点,会尽可能的控制在最少
  • 在之前的 renderRoot 结束的时候,会在 root 上面设置一个 finishedWork 这么一个对象
  • 这个对象是 RootFiber 对象, 这个对象包含了 firstEffect 到 lastEffect 单项链
  • 这个链就包含在整个 render 阶段,计算出来的需要在 commit 阶段进行操作的节点
  • 这些节点是在commit阶段根据赋值给它们的不同的 SideEffect 进行的不同的操作
  • commitRoot 这个方法
    • 包含很多去处理不同 SideEffect 的操作
    • 包含3个循环, 这3个循环都是对 firstEffect 到 lastEffect 单项链表上面
    • 每一个节点上面去更新内容,在更新完之后做的一些善后工作

2 )源码

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

定位到 commitRoot

function commitRoot(root: FiberRoot, finishedWork: Fiber): void {// 一进来,它先设置了两个全局,变量是isworking和iscommitting// isworking,我们在 renderRoot 的过程当中也会设置为 true// 所以说 isWorking 这个全局变量,它就代表着我们正在进行更新的操作, 不管是它在哪一个阶段// isCommitting 就是单独的指明正在处于commit阶段,即提交的阶段isWorking = true;isCommitting = true;startCommitTimer();invariant(root.current !== finishedWork,'Cannot commit the same tree as before. This is probably a bug ' +'related to the return field. This error is likely caused by a bug ' +'in React. Please file an issue.',);// 进入之后,通过 root.pendingCommitExpirationTime,获取 committedExpirationTime// 这个 pendingCommitExpirationTime 就是在 onCommitRoot 的时候设置的// 在之前 render 阶段执行操作的 expirationTimeconst committedExpirationTime = root.pendingCommitExpirationTime;invariant(committedExpirationTime !== NoWork,'Cannot commit an incomplete root. This error is likely caused by a ' +'bug in React. Please file an issue.',);// 设置这个 root.pendingCommitExpirationTime 为 NoWork// 因为即将要对这个 committedExpirationTime 来进行一个commit的操作了// 接下去就是在执行完之后, root.pendingCommitExpirationTime 肯定就是已经没有了root.pendingCommitExpirationTime = NoWork;// Update the pending priority levels to account for the work that we are// about to commit. This needs to happen before calling the lifecycles, since// they may schedule additional updates.// 这边会做一个标记,就是 markCommittedPriorityLevels 这是标记我们的一个优先级// 它标记的结果是根据 finishedWork.expirationTime 以及 finishedWork.childExpirationTime// 因为 finishedWork 是一个 RootFiber,所以它的 childExpirationTime 代表着所有子树当中优先级最高的那一个任务// 而它的 expirationTime,大部分情况下都应该是跟它的 childExpirationTime 是相同的,但是会有一些情况不一样// 比如说通过外部强制指定的方式, 比如在有 nextRenderDidError 的情况下// 会设置它的 experiencetime 等于 Sync 这就是从外部去强制指定它的一个 expirationTime// 在这种情况下,它的 expirationTime 跟它的 childExpirationTime 会有比较大的区别// 当然并不是说没有这种外部指定,它们一定相同,大部分情况下,它们应该是相同的const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;const earliestRemainingTimeBeforeCommit =childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit? childExpirationTimeBeforeCommit: updateExpirationTimeBeforeCommit;markCommittedPriorityLevels(root, earliestRemainingTimeBeforeCommit);let prevInteractions: Set<Interaction> = (null: any);if (enableSchedulerTracing) {// Restore any pending interactions at this point,// So that cascading work triggered during the render phase will be accounted for.prevInteractions = __interactionsRef.current;__interactionsRef.current = root.memoizedInteractions;}// Reset this to null before calling lifecyclesReactCurrentOwner.current = null;// 它判断了 finishedWork,也就是我们的 RootFiber,它的 effectTag 是否大于 PerformedWork// 因为 PerformedWork 是给 DEV Tool 用的, 所以在整体的更新流程当中是没有意义的// 它大于 PerformedWork 代表它上面有 SideEffect,它也需要被 committed// 所以这边通过这么一个判断,如果是大于 PerformedWork 的了// 要把 finishedWork 的也作为一个 effect 来增加到它自己的 firstEffect 到 lastEffect 的这个链上面// 下面是增加的过程,因为要判断它目前上面是否有 SideEffect,以不同的方式插入到整个单链表的最后面let firstEffect;if (finishedWork.effectTag > PerformedWork) {// A fiber's effect list consists only of its children, not itself. So if// the root has an effect, we need to add it to the end of the list. The// resulting list is the set that would belong to the root's parent, if// it had one; that is, all the effects in the tree including the root.if (finishedWork.lastEffect !== null) {finishedWork.lastEffect.nextEffect = finishedWork;firstEffect = finishedWork.firstEffect;} else {firstEffect = finishedWork;}} else {// There is no effect on the root.firstEffect = finishedWork.firstEffect;}prepareForCommit(root.containerInfo);// Invoke instances of getSnapshotBeforeUpdate before mutation.nextEffect = firstEffect;startCommitSnapshotEffectsTimer();// 接下去就进入了三个循环:// 第一个循环主要是 commitBeforeMutationLifecycles,这个方法它其实很简单,它是这三个循环当中最简单的一个方法// 它唯一的作用就是调用 ClassComponent 上面可能会存在的 getSnapshotBeforeUpdate 这么一个生命周期方法while (nextEffect !== null) {let didError = false;let error;if (__DEV__) {invokeGuardedCallback(null, commitBeforeMutationLifecycles, null);if (hasCaughtError()) {didError = true;error = clearCaughtError();}} else {try {commitBeforeMutationLifecycles();} catch (e) {didError = true;error = e;}}if (didError) {invariant(nextEffect !== null,'Should have next effect. This error is likely caused by a bug ' +'in React. Please file an issue.',);captureCommitPhaseError(nextEffect, error);// Clean-upif (nextEffect !== null) {nextEffect = nextEffect.nextEffect;}}}stopCommitSnapshotEffectsTimer();if (enableProfilerTimer) {// Mark the current commit time to be shared by all Profilers in this batch.// This enables them to be grouped later.recordCommitTime();}// Commit all the side-effects within a tree. We'll do this in two passes.// The first pass performs all the host insertions, updates, deletions and// ref unmounts.nextEffect = firstEffect;startCommitHostEffectsTimer();// 第二个循环调用的方法是 commitAllHostEffects// 这个就是主要是操作对于 dom 节点它需要去做的一些内容// 比如说如果这个 dom 节点是刚刚新增的,那么要执行一个插入的操作// 如果这个 dom 节点它需要被删除,我们要执行删除的操作// 如果这个 dom 节点它的属性或者它 children 有更新,那么要执行一个更新的操作while (nextEffect !== null) {let didError = false;let error;if (__DEV__) {invokeGuardedCallback(null, commitAllHostEffects, null);if (hasCaughtError()) {didError = true;error = clearCaughtError();}} else {try {commitAllHostEffects();} catch (e) {didError = true;error = e;}}if (didError) {invariant(nextEffect !== null,'Should have next effect. This error is likely caused by a bug ' +'in React. Please file an issue.',);captureCommitPhaseError(nextEffect, error);// Clean-upif (nextEffect !== null) {nextEffect = nextEffect.nextEffect;}}}stopCommitHostEffectsTimer();resetAfterCommit(root.containerInfo);// The work-in-progress tree is now the current tree. This must come after// the first pass of the commit phase, so that the previous tree is still// current during componentWillUnmount, but before the second pass, so that// the finished work is current during componentDidMount/Update.root.current = finishedWork;// In the second pass we'll perform all life-cycles and ref callbacks.// Life-cycles happen as a separate pass so that all placements, updates,// and deletions in the entire tree have already been invoked.// This pass also triggers any renderer-specific initial effects.nextEffect = firstEffect;startCommitLifeCyclesTimer();// 接下去再做第三个方法叫做 commitAllLifeCycles// 这个就是跟组件还有各种各样的所有东西相关的生命周期的方法都会在这里面被调用while (nextEffect !== null) {let didError = false;let error;if (__DEV__) {invokeGuardedCallback(null,commitAllLifeCycles,null,root,committedExpirationTime,);if (hasCaughtError()) {didError = true;error = clearCaughtError();}} else {try {commitAllLifeCycles(root, committedExpirationTime);} catch (e) {didError = true;error = e;}}if (didError) {invariant(nextEffect !== null,'Should have next effect. This error is likely caused by a bug ' +'in React. Please file an issue.',);captureCommitPhaseError(nextEffect, error);if (nextEffect !== null) {nextEffect = nextEffect.nextEffect;}}}if (enableHooks &&firstEffect !== null &&rootWithPendingPassiveEffects !== null) {// This commit included a passive effect. These do not need to fire until// after the next paint. Schedule an callback to fire them in an async// event. To ensure serial execution, the callback will be flushed early if// we enter rootWithPendingPassiveEffects commit phase before then.let callback = commitPassiveEffects.bind(null, root, firstEffect);if (enableSchedulerTracing) {// TODO: Avoid this extra callback by mutating the tracing ref directly,// like we do at the beginning of commitRoot. I've opted not to do that// here because that code is still in flux.callback = Schedule_tracing_wrap(callback);}passiveEffectCallbackHandle = Schedule_scheduleCallback(callback);passiveEffectCallback = callback;}// 以上3个循环中,三个方法调用完之后,我们的commit阶段就算已经完成了// 然后它会把这些全局变量设置为 false// 以及它会调用一个叫 onCommitRoot 的这么一个方法isCommitting = false;isWorking = false;stopCommitLifeCyclesTimer();stopCommitTimer();onCommitRoot(finishedWork.stateNode);if (__DEV__ && ReactFiberInstrumentation.debugTool) {ReactFiberInstrumentation.debugTool.onCommitWork(finishedWork);}// 它执行一段相关的 expirationTime 的判断, 它为什么要这边再做一次?// 因为我们在执行 ClassComponent 的生命周期方法的过程当中,可能又会产生新的更新// 产生新的更新的时候,RootFiber的 childExpirationTime 它又有可能会变化// 它不会把这一部分代码放到最前面去做,而是要放到最后面来做// 就是因为它需要等到我们如果在生命周期方法里面又创建了新的 update// 又产生了新的 childExpirationTime 的时候,那么再来进行这么一个判断// 这个判断其实主要做的事情,也就是设置一个全局变量// 叫做 legacyErrorBoundariesThatAlreadyFailed ,把它设置为 nullconst updateExpirationTimeAfterCommit = finishedWork.expirationTime;const childExpirationTimeAfterCommit = finishedWork.childExpirationTime;const earliestRemainingTimeAfterCommit =childExpirationTimeAfterCommit > updateExpirationTimeAfterCommit? childExpirationTimeAfterCommit: updateExpirationTimeAfterCommit;if (earliestRemainingTimeAfterCommit === NoWork) {// If there's no remaining work, we can clear the set of already failed// error boundaries.legacyErrorBoundariesThatAlreadyFailed = null;}// 这里调用了一个方法叫做 onCommit// 这个 earliestRemainingTimeAfterCommit 是优先级较小,值较大的 expirationTime// 它就变成了新的root上面的 expirationTimeonCommit(root, earliestRemainingTimeAfterCommit);// 忽略 polyfill 相关的if (enableSchedulerTracing) {__interactionsRef.current = prevInteractions;let subscriber;try {subscriber = __subscriberRef.current;if (subscriber !== null && root.memoizedInteractions.size > 0) {const threadID = computeThreadID(committedExpirationTime,root.interactionThreadID,);subscriber.onWorkStopped(root.memoizedInteractions, threadID);}} catch (error) {// It's not safe for commitRoot() to throw.// Store the error for now and we'll re-throw in finishRendering().if (!hasUnhandledError) {hasUnhandledError = true;unhandledError = error;}} finally {// Clear completed interactions from the pending Map.// Unless the render was suspended or cascading work was scheduled,// In which case– leave pending interactions until the subsequent render.const pendingInteractionMap = root.pendingInteractionMap;pendingInteractionMap.forEach((scheduledInteractions, scheduledExpirationTime) => {// Only decrement the pending interaction count if we're done.// If there's still work at the current priority,// That indicates that we are waiting for suspense data.if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {pendingInteractionMap.delete(scheduledExpirationTime);scheduledInteractions.forEach(interaction => {interaction.__count--;if (subscriber !== null && interaction.__count === 0) {try {subscriber.onInteractionScheduledWorkCompleted(interaction);} catch (error) {// It's not safe for commitRoot() to throw.// Store the error for now and we'll re-throw in finishRendering().if (!hasUnhandledError) {hasUnhandledError = true;unhandledError = error;}}}});}},);}}
}
  • 进入 onCommit
    function onCommit(root, expirationTime) {root.expirationTime = expirationTime;root.finishedWork = null; // 因为 finishedWork 已经被 commit 掉了
    }
    
  • 以上就是整个 commitRoot的一个流程, 细节都写在代码注释中了
  • 这个流程当中,这三个循环看起来复杂,实际上很好阅读, 后续会看下相关的细节实现

这篇关于React16源码: React中commit阶段的commitRoot的主流程源码实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

mybatis执行insert返回id实现详解

《mybatis执行insert返回id实现详解》MyBatis插入操作默认返回受影响行数,需通过useGeneratedKeys+keyProperty或selectKey获取主键ID,确保主键为自... 目录 两种方式获取自增 ID:1. ​​useGeneratedKeys+keyProperty(推

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

基于Python实现一个图片拆分工具

《基于Python实现一个图片拆分工具》这篇文章主要为大家详细介绍了如何基于Python实现一个图片拆分工具,可以根据需要的行数和列数进行拆分,感兴趣的小伙伴可以跟随小编一起学习一下... 简单介绍先自己选择输入的图片,默认是输出到项目文件夹中,可以自己选择其他的文件夹,选择需要拆分的行数和列数,可以通过

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核