【React原理 - 任务调度和时间分片详解】

概述

在React15的时候,React使用的是从根节点往下递归的方式同步创建虚拟Dom,由于递归具有同步不可中断的特性,所以当执行长任务时(通常以60帧为标准,即16.6ms)就会长时间占用主线程长时间无响应,导致页面卡顿,对于交互及其不友好。所以React16中新增了fiber架构和scheduler调度(之前只有渲染器Renderer、协调器Reconciler),在该版本中新增了时间分片逻辑,将一个长任务切分为多个小任务,并在浏览器空闲时,根据优先级执行。其中时间分片的前提就是中断和恢复。本文以[email protected]来解释React中的时间分片以及中断和恢复原理。

调度器Scheduler

在了解时间分片之前,先了解下调度器是什么?
所谓Scheduler,如名所示就是用于任务调度,判断在什么时候执行任务避免长时间阻塞主线程的一个工具包。React官方将其设置为独立的包,不仅仅用于React,当其他项目需要对长任务进行优化调度时都可以使用该包,所以其命名为单独的scheduler,而不是react-scheduler
其主要有两个特性:

  • 优先级,根据优先级高低决定执行哪个任务,优先执行高优先级任务。
  • 时间分片,其内部设置了5ms的执行时间,当任务执行超过设置的时间则会中断执行,并将主线程交回浏览器,避免长时间无响应导致卡顿,在浏览器空闲时再通过恢复来继续执行中断的任务。

针对上面的两个特性,我们来一一解释:

优先级

在React内部优先级有三种:Event优先级Lane优先级Scheduler优先级

Event优先级:事件优先级,React会将原生事件封装成合成事件时,会根据事件类型携带不同的事件优先级。Event优先级定义时就映射了Lane优先级,即使用Lane优先级来进行的赋值。

// react/packages/react-reconciler/src/ReactEventPriorities.js
export const NoEventPriority: EventPriority = NoLane; // 无优先级
export const DiscreteEventPriority: EventPriority = SyncLane; // 离散优先级
export const ContinuousEventPriority: EventPriority = InputContinuousLane; // 连续输入优先级
export const DefaultEventPriority: EventPriority = DefaultLane; // 默认优先级
export const IdleEventPriority: EventPriority = IdleLane; // 空闲优先级

Lane优先级:车道优先级,位于react-reconciler协调器中,用于规定更新任务的执行顺序。

为什么是车道优先级呢? 可以看定义该优先级是使用二进制定义的,一方面二进制运算性能是很好的,其次React采用的是位优先级,不同的位表示具有该优先级,然后通过位运算能快速获取优先级。然后看后面的二进制定义,是不是像车道的样子,所以命名为车道优先级也表示其是使用二进制定义的。

// react/packages/react-reconciler/src/ReactFiberLane.js
export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;

export const SyncHydrationLane: Lane = /*               */ 0b0000000000000000000000000000001;
export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000010;
export const SyncLaneIndex: number = 1;

export const InputContinuousHydrationLane: Lane = /*    */ 0b0000000000000000000000000000100;
export const InputContinuousLane: Lane = /*             */ 0b0000000000000000000000000001000;

export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000000010000;
export const DefaultLane: Lane = /*          

Scheduler优先级:调度优先级,位于scheduler中,用于制定更新任务调度的执行顺序。

// react/packages/scheduler/src/SchedulerPriorities.js
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;

// TODO: Use symbols?
export const NoPriority = 0; // 无优先级
export const ImmediatePriority = 1; // 离散事件,点击、keydown、input这种单个事件
export const UserBlockingPriority = 2; // 连续输入优先级,滚动、拖拽等连续事件
export const NormalPriority = 3; // 普通优先级(默认优先级)
export const LowPriority = 4; // 低优先级
export const IdlePriority = 5; // 空闲优先级

并且每种调度优先级都有自己的一个timeout,在unstable_scheduleCallback创建任务时会根据这个时延来设置expirationTime,并进一步设置sortIndex。这里只是介绍下,知道有这个东西,下面会详细说明。

// react/packages/scheduler/src/SchedulerFeatureFlags.js
export const enableSchedulerDebugging = false;
export const enableProfiling = false;
export const frameYieldMs = 5;

export const userBlockingPriorityTimeout = 250;
export const normalPriorityTimeout = 5000;
export const lowPriorityTimeout = 10000;

转换体系:Scheduler是独立的一个包,所以自己内部维护了一个优先级(Scheduler优先级),所以需要进行优先级转换:Lane优先级 -> Event优先级 -> Scheduler优先级进行Lane优先级和Scheduler优先级的映射。比如当我们点击按钮触发onclick事件时,由于合成事件携带了Event优先级,并且该Event优先级是Lane优先级的映射,然后在协调器中创建任务调度时,会根据该Event优先级转换为Scheduler优先级。如下所示:在下面代码中创建调度任务时会执行scheduleTaskForRootDuringMicrotask函数,并在其中会根据lane优先级获取Event优先级,然后转换为Scheduler优先级

// react/packages/react-reconciler/src/ReactEventPriorities.js
export function lanesToEventPriority(lanes: Lanes): EventPriority {
   
   
  const lane = getHighestPriorityLane(lanes);
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
   
   
    return DiscreteEventPriority;
  }
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
   
   
    return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) {
   
   
    return DefaultEventPriority;
  }
  return IdleEventPriority;
}

// react/packages/react-reconciler/src/ReactFiberRootScheduler.js
import {
   
   
  ImmediatePriority as ImmediateSchedulerPriority,
  UserBlockingPriority as UserBlockingSchedulerPriority,
  NormalPriority as NormalSchedulerPriority,
  IdlePriority as IdleSchedulerPriority,
  cancelCallback as Scheduler_cancelCallback,
  scheduleCallback as Scheduler_scheduleCallback,
  now,
} from './Scheduler';

function scheduleTaskForRootDuringMicrotask(
  root: FiberRoot,
  currentTime: number,
): Lane {
   
   
// ...
	let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
   
   
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值