监理建设协会网站,营销推广方法,外贸网站 wordpress,企业网站宣传schedule调度的整体流程
React Fiber Scheduler 是 react16 最核心的一部分#xff0c;这块在 react-reconciler 这个包中这个包的核心是 fiber reconciler#xff0c;也即是 fiber 结构fiber的结构帮助我们把react整个树的应用#xff0c;更新的流程#xff0c;能够拆成每…schedule调度的整体流程
React Fiber Scheduler 是 react16 最核心的一部分这块在 react-reconciler 这个包中这个包的核心是 fiber reconciler也即是 fiber 结构fiber的结构帮助我们把react整个树的应用更新的流程能够拆成每一个fibrer对象为单元的一个更新的流程这种单元的形式把更新拆分出来之后给每个不同的任务提供一个优先级以及我们在更新的过程当中可以中断因为我们记录更新到了哪一个单元中断了之后可以过一会儿再回过头来继续从这个单元开始继续之前没有做完的更新 在 react 16之前 setState 产生的更新必须从头到尾更新完成然后再执行之后的代码如果我们的整个应用树它的节点非常多整个更新会导致它占用的js的运行时间会非常的多让页面的其他的一些操作进入一个停滞的状态比如说动画的刷新或者是我们在 inpput 里面输入内容的时候可能产生卡顿的感觉 所以react 16之后它的整体的更新流程是完全不一样的 因为加入了中断挂起这样的功能导致它的整个更新流程的调度变得非常的复杂整个源码体系它每一个细节每一个变量的具体作用都是值得琢磨的理解它出于什么目的这么去设计这时候才能深入整个react的更新体系中这样才能慢慢理解
全局变量一览
调度过程中的全局变量基本上都在 react-reconciler/src/ReactFiberScheduler.js 这个js里面 https://github.com/facebook/react/blob/v16.6.3/packages/react-reconciler/src/ReactFiberScheduler.js 这个js的代码是非常多的它总共有两千五百行代码而且注释不算多在这个文件里面会存在着非常多的公共变量就是说我们定义在这个文件的顶层的变量名就是说我们定义在这个文件顶层作用域上面的很多的变量在很多方法里面它们是被共享的这些公共变量对于去理解整个 schedule它是非常重要的因为它在很多方法里面都会有调用 它什么时候调用什么时候被修改成什么值用来记录什么内容对于这些公共变量的理解一方面来说比较的困难另外一方面来说它非常的重要如果不能理解这些公共变量的作用会导致看源码的时候看到一些地方会变得毫无头绪
几个重点需要关注的变量
// Used to ensure computeUniqueAsyncExpiration is monotonically decreasing.
let lastUniqueAsyncExpiration: number Sync - 1;let isWorking: boolean false;// The next work in progress fiber that were currently working on.
let nextUnitOfWork: Fiber | null null;
let nextRoot: FiberRoot | null null;
// The time at which were currently rendering work.
let nextRenderExpirationTime: ExpirationTime NoWork;
let nextLatestAbsoluteTimeoutMs: number -1;
let nextRenderDidError: boolean false;// The next fiber with an effect that were currently committing.
let nextEffect: Fiber | null null;let isCommitting: boolean false;
let rootWithPendingPassiveEffects: FiberRoot | null null;
let passiveEffectCallbackHandle: * null;
let passiveEffectCallback: * null;let legacyErrorBoundariesThatAlreadyFailed: Setmixed | null null;// Used for performance tracking.
let interruptedBy: Fiber | null null;let stashedWorkInProgressProperties;
let replayUnitOfWork;
let mayReplayFailedUnitOfWork;
let isReplayingFailedUnitOfWork;
let originalReplayError;
let rethrowOriginalError;isWorking commitRoot和renderRoot开始都会设置为true然后在他们各自阶段结束的时候都重置为false用来标志是否当前有更新正在进行不区分阶段 isCommitting commitRoot开头设置为true结束之后设置为false用来标志是否处于commit阶段 nextUnitOfWork 用于记录render阶段Fiber树遍历过程中下一个需要执行的节点。在resetStack中分别被重置它只会指向workInProgress nextRoot nextRenderExpirationTime 用于记录下一个将要渲染的root节点和下一个要渲染的任务的 nextEffect 用于commit阶段记录firstEffect - lastEffect链遍历过程中的每一个Fiber
下面是其他的一些全局变量
// Linked-list of roots
let firstScheduledRoot: FiberRoot | null null;
let lastScheduledRoot: FiberRoot | null null;let callbackExpirationTime: ExpirationTime NoWork;
let callbackID: *;
let isRendering: boolean false;
let nextFlushedRoot: FiberRoot | null null;
let nextFlushedExpirationTime: ExpirationTime NoWork;
let lowestPriorityPendingInteractiveExpirationTime: ExpirationTime NoWork;
let hasUnhandledError: boolean false;
let unhandledError: mixed | null null;let isBatchingUpdates: boolean false;
let isUnbatchingUpdates: boolean false;let completedBatches: ArrayBatch | null null;let originalStartTimeMs: number now();
let currentRendererTime: ExpirationTime msToExpirationTime(originalStartTimeMs,
);
let currentSchedulerTime: ExpirationTime currentRendererTime;// Use these to prevent an infinite loop of nested updates
const NESTED_UPDATE_LIMIT 50;
let nestedUpdateCount: number 0;
let lastCommittedRootDuringThisBatch: FiberRoot | null null;firstScheduledRoot lastScheduledRoot 用于存放有任务的所有root的单链表结构在findHighestPriorityRoot用来检索优先级最高的root在addRootToSchedule中会修改 callbackExpirationTime callbackID 记录请求ReactScheduler的时候用的过期时间如果在一次调度期间有新的调度请求进来了而且优先级更高那么需要取消上一次请求如果更低则无需再次请求调度。callbackID是ReactScheduler返回的用于取消调度的 ID isRendering performWorkOnRoot开始设置为true结束的时候设置为false表示进入渲染阶段这是包含render和commit阶段的 nextFlushedRoot nextFlushedExpirationTime 用来标志下一个需要渲染的root和对应的expirtaionTime deadline deadlineDidExpire deadline是ReactScheduler中返回的时间片调度信息对象用于记录是否时间片调度是否过期在shouldYield根据deadline是否过期来设置 isBatchingUpdates isUnbatchingUpdates isBatchingInteractiveUpdates batchedUpdates、unBatchedUpdatesdeferredUpdates、interactiveUpdates等这些方法用来存储更新产生的上下文的变量 originalStartTimeMs 固定值js 加载完一开始计算的时间戳 currentRendererTime currentSchedulerTime 计算从页面加载到现在为止的毫秒数后者会在isRendering true的时候用作固定值返回不然每次requestCurrentTime都会重新计算新的时间。 以上每一个全局变量给它拿出来单独解释它是在什么地方被用到 以及它是用来记录哪些东西是在什么情况下才会发挥了哪些作用
调度流程 1 第一阶段 在调用 ReactDOM.render, setState, forceUpdate都会产生一个update产生update之后进入 scheduleWork 进行调度 scheduleWork 第一步操作是 addRootToScheduler在一个rect应用当中它不仅仅可能只存在一个 root 节点因为我们通过 ReactDOM.render 调用的时候就会创建一个 root 节点如果调用多次 ReactDOM.render就可以创建多个 root 节点这个时候, 整个应用中就会存在着多个react的节点在这些节点可以单独在内部进行 setState进行调度它们都会有独立的 updateQueen有独立的一个 fiber tree 来进行应用的更新一个应用当中可能会存在多个root, 所以这个时候就要去维护一下因为在同一时间可能有多个root会有更新存在, 所以有这么一个地方去维护它这就是 addRootToScheduler 的一个作用 addRootToScheduler 加入之后, 要先判断一下是否正在 render 阶段 或者 前后的root不同 如果是则调用 requestWork就要开始进行工作了如果不是我们就要 return因为之前的任务可能正在做或者处于目前这个阶段不需要主动的再去调用一个 requestWork 来更新了 关于 requestWork 它里面做了什么 它判断 expirationTime它是否是 Sync计算 expirationTime 调用的是 computeExpirationForFiber这时候会根据 fiber 是否有 ConcurrentMode 的特性来计算 Sync 的 expirationTime 或者是异步的 expirationTime这个时候它最终会导致整体的一个更新模式的不同因为如果是 Sync 的模式代表着我们这个更新要立马进行执行要立马更新到最终的 dom tree 上面所以我们调用的是 performSyncWork而如果它是一个 Async 模式的那么说明它的优先级不是特别高那么他会进入一个调度的流程因为它可以不立即更新它本身的期望是在 expirationTime 结束之前能够被更新完成就可以了, 所以它的优先级非常低会进入到一整个调度的流程即 scheduleCallbackWithExpirationTime
2 下一阶段 整个调度的流程 react 给它单独区分了一个包 packages/scheduler 用蓝色的框给它圈起来叫做 async schedule work 这一部分就涉及到整个异步的调度的过程 它利用的是浏览器当中一个较新的API叫做 requestIdleCallback, 能够让浏览器优先进行他自己的任务比如说更新动画在每一帧有多余的时间的时候它调用react给他设置了一个callback然后就可以去执行react一个更新然后react会自己去记一个时在这个时间内我可以执行我自己的工作如果这个时间内我的工作没有执行完我要把javascript的运行的主动权交还给浏览器让浏览器去执行它新的一些动画的更新之类的来让浏览器保证高优先级的任务能够被立即执行所以这就是这个蓝色这一片区域的一个作用 在这里面调用的一个方法叫做 scheduleDeferredCallback, 然后会有一个 callbackList因为可能多次调用这个方法去把 callback 设置进去然后在这里面我们虽然想要使用 requestIdleCallback 这个API但是, 大部分浏览器还不支持, 浏览器的兼容性也不是特别好所以在react里面它实现了自己的一个模拟 requestIdleCallback 的一个方式 它通过 requestAnimationFrame 和 js 的任务队列的原理来进行了一个模拟 在调用 requestIdleCallback 之后说明浏览器有空了可以去执行react的更新了 这就是我们加入到这里面的异步的更新任务它的优先级比较低浏览器有空的时候再来执行因为 react 的任务它是有一个 expirationTime 的所以它这里要判断一下我的任务有没有超时如果已经超时了要把所有加入callbackList队列的超时任务都执行掉因为任务已经超时了所以必须要立刻完成执行到第一个非超时的任务之后若还有时间可以继续执行如果没有时间了要把主动权交还给浏览器让浏览器来做其他一些任务 最终要执行这个任务执行的是什么 调用一个 performAsyncWork 这个方法它会执行react的schedule里面的回调函数在调用这个方法的时候schedule 会传给这个方法一个叫做deadline的一个对象这个对象是用来判断。在进入 performAsyncWork 的时候就进入到react的一个更新流程react的更新流程中它去遍历整一棵树会遍历每棵树的每个单元然后对它进行一个更新的操作每个单元更新完了之后回过头来通过这个deadline对象判断一下现在是否还有 js 运行的一个时间片因为调度器每一次调用 performAsyncWork 的任务它是有一个时间限制的比如说默认的情况下是22毫秒在这个时间片内你可以去执行的操作这就是这个 deadline 对象它的一个作用 最终调用 performWork 这个方法performWork 它调用的是没有deadline的performAsyncWork 它调用的是有deadline的最终在 if deadline 这里汇集在一起根据是否有 deadline 进入下个阶段的循环
3 第三个阶段 根据是否有 deadline 进入循环这个循环是什么呢 这个循环就是要遍历整棵树每一个 fiber 节点进行的更新操作 对于同步任务它会遍历完整棵树然后把整个应用更新就完了 因为它是同步的跟以前的react用法是一样的 对于异步的来讲如果符合条件 进入 performWorkOnRoot, 它做的其实是一个更新的过程然后 findHighestPriorityRoot 找到一个最高优先级的节点之后对这个节点进行一个更新 recomputeCurrentRendererTime对于有 deadline 的情况调用 performWorkOnRoot 进行更新任务之后在 renderRoot 里面它还会有一个循环去判断 deadline最终要等这个 performWorkOnRoot 返回之后才会继续下面的操作对于有 deadline 的情况会重新请求一个时间然后去判断一下deadline是否已经过期如果已经过期的话会回过头来到红色区域再进行一个判断如果发现 deadline 已经过期的话又会去继续调用这个 scheduleCallbackWithExpirationTime再次进行异步的回调, 它这是一个递归的过程因为之前第一阶段加入了一个 addRootToScheduler它就有一个队列在维护着所有的更新的情况对于每一次更新只能更新一个优先级的任务以及一个root上的任务上述红色这块区域它就是一个循环的条件判断它每次更新一个节点上的一个优先级任务具体的操作在 performWorkOnRoot 里面更新完之后, 它会去调用相对应的方法在这个root上对应的优先级任务更新完之后它要找到下一个root上面的对应优先级的任务然后再次进入这个循环所以这个就是deadline它的一个用处它帮助我们去判断是否应该跳出循环了如果我们一直处于这个循环要把所有任务都更新完那么可能占用的js运行时间会非常的长导致可能的动画停滞或用户输入卡顿。在这个 deadline 超过了之后这个循环直接跳出然后再继续跳回到这个 scheduleCallbackWithExpirationTime再次进入一个调度然后把js的执行权交给浏览器让它先执行动画或者用户输入的响应等有空了再回过头来再去执行这个任务然后又回到 红色区域判断这里之前没完成的任务再继续这么一个循环最终达到的目的是要把 root 里面的所有的节点上面的所有更新都执行完为止这就是整的一个循环的一个过程
整体流程图 总结
通过 ReactDOM.render, setState, forceUpdate 这几个方法产生了更新产生了更新之后维护一个队列去保存这些更新以及对应的root节点然后根据它任务的优先级来进行判断判断是同步的更新还是异步的更新 对于异步的更新如果有多个任务它会一直处于先执行浏览器优先级最高的更新有空的时候回过头来更新 react 树如果 root 上对应的某一个优先级的任务更新完了那么先输出到dom上然后执行下一个更新在这个循环的过程当中根据这个调度器传入的 deadline 对象判断是否有超时如果超时再回过头去进行一个调度先把执行权给浏览器让它保证动画的流畅运行等它有空再回过头来继续执行的任务 这就是整个调度的核心原理目的是 保证我们低优先级的react更新不会阻止浏览器的一些高要求的动画更新能够保证浏览器的一些动画能够达到30帧以上这么一个情况 以上就是react整个的调度过程