【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
<think>好的,我现在需要回答用户关于React原理高频面试题的问题。首先,我得整理React的核心原理,确保自己理解正确,然后回忆常见的面试题目,并组织成结构清晰的回答。 首先,React原理部分。我应该从虚拟DOM开始,这是React高效更新的关键。虚拟DOM通过比较新旧树结构,计算出最小变更,再应用到真实DOM上,这个过程叫做协调(Reconciliation)。接下来是Diff算法,React的Diff策略是同级比较,通过key属性优化列表渲染效率。然后是组件化开发,React组件分为函数组件类组件,现在主推函数组件结合Hooks。数据驱动视图方面,React采用单向数据流,状态提升到父组件或使用状态管理库。最后是Fiber架构,这是React 16引入的,解决大型应用卡顿问题,支持异步可中断更新,提高性能。 然后是高频面试题。常见的题目包括虚拟DOM的工作原理、Diff算法细节、类组件与函数组件的区别、Hooks的使用规则原理、setState的同步异步问题、性能优化方法、Fiber架构的目标、React事件机制、状态管理工具比较,以及SSR的实现优缺点。每个问题都需要简明扼要的回答,突出重点,比如虚拟DOM的优点Diff策略,Hooks的规则常见API,Fiber如何拆分任务等。 需要注意用户要求使用中文,数学公式的正确格式,但可能这部分涉及公式不多,不过要确保技术术语准确。结构上要分点说明,原理面试题分开,每个部分用标题子标题组织,便于阅读。同时,回答要真实可靠,避免错误,比如Fiber架构的具体实现细节可能比较复杂,需要简明解释。 可能存在的疑问点:是否覆盖了所有高频面试题?比如React生命周期可能在函数组件中不再强调,但类组件中可能还会问,但用户提到现在主推函数组件,所以可能重点在Hooks。另外,是否需要解释JSX?虽然用户没提到,但作为原理的一部分,可能简要提及JSX编译成React元素的过程。 总结,组织内容时先讲原理,再列面试题,每个问题给出关键点,保持清晰结构,使用用户易懂的语言,避免过于技术化的同时准确传达概念。</think>### React 原理详解 #### 1. **虚拟DOM(Virtual DOM)** - **核心机制**:通过JavaScript对象模拟真实DOM结构,减少直接操作DOM的开销。 - **工作流程**: 1. 状态变化时生成新虚拟DOM; 2. 对比新旧虚拟DOM(Diff算法); 3. 将差异部分批量更新到真实DOM。 - **优点**:提升性能,避免频繁重绘。 #### 2. **Diff算法** - **策略**: - **同级比较**:仅对比同一层级的节点; - **Key优化**:列表元素添加唯一`key`,帮助React识别元素稳定性; - **类型比对**:节点类型不同直接替换子树。 - **数学表达**:假设树节点数为 $n$,Diff时间复杂度优化为 $O(n)$。 #### 3. **组件化与数据流** - **组件类型**: - **函数组件**:无状态(通过Hooks管理状态); - **类组件**:包含生命周期状态(逐渐被Hooks替代)。 - **数据流**:单向数据流(父 → 子),复杂场景需搭配Redux、Context等状态管理工具。 #### 4. **Fiber架构** - **目标**:解决大型应用卡顿问题,支持异步渲染。 - **原理**: - 将渲染任务拆分为多个“纤维单元”(Fiber Node); - 可中断、可恢复,优先处理高优先级任务(如动画)。 --- ### React高频面试题 #### 1. **虚拟DOM一定比真实DOM快吗?** - **答案**:不一定。虚拟DOM的优势在复杂视图的批量更新,简单操作可能直接操作DOM更快。 #### 2. **React Diff算法的优化策略** - **关键点**:同级比较、Key值优化、类型比对。 #### 3. **Hooks的使用限制** - **规则**: 1. 只在函数组件顶层调用; 2. 不能在循环/条件语句中使用。 - **原理**:Hooks依赖调用顺序,React通过链表结构管理状态。 #### 4. **setState是同步还是异步?** - **答案**: - 合成事件生命周期中为“异步”(批量更新); - 在`setTimeout`或原生事件中为同步。 #### 5. **性能优化手段** - **常用方法**: - `React.memo`(函数组件缓存); - `useMemo`/`useCallback`(避免重复计算); - 列表添加`key`; - 按需加载(`React.lazy` + `Suspense`)。 #### 6. **Fiber如何解决卡顿问题?** - **答案**:任务分片、可中断机制、优先级调度。 #### 7. **React事件机制** - **原理**:事件委托到根节点(如`document`),通过合成事件(SyntheticEvent)抹平浏览器差异。 #### 8. **Redux vs Context API** - **Redux**:适合复杂状态管理,支持中间件(如异步处理); - **Context**:轻量级,适合局部状态共享。 --- ### 总结 React通过虚拟DOM、组件化、Fiber架构等机制实现高效渲染,面试需重点掌握其核心原理Hooks的使用场景。建议结合实际项目经验,理解性能优化状态管理方案。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值