React源码解读(三)React调度过程

React调度过程

React 的调度过程是其核心机制之一,它确保了 React 应用能够高效地处理不同优先级的更新任务,提升用户体验和应用性能。

1. 更新触发

React 应用中的更新可以由多种情况触发,常见的有:

  • 状态更新:组件内部调用 setState(类组件)或 useState 的更新函数(函数组件)来改变状态,从而触发更新。
import React, { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);
    const handleClick = () => {
        setCount(count + 1);
    };
    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={handleClick}>Increment</button>
        </div>
    );
}
  • 属性更新:父组件传递给子组件的属性发生变化时,子组件会接收到新的属性,从而触发更新。
  • 上下文更新:当 React 上下文(Context)的值发生变化时,使用该上下文的组件会触发更新。

2. 优先级分配

在 React 中,不同类型的更新会被分配不同的优先级,这是通过 Lane 模型来实现的。Lane 本质上是一个 number 类型,通过二进制位来表示不同的优先级通道。常见的优先级类型有:

  • 同步优先级(SyncLane):用于处理同步更新,如用户输入事件(点击、输入等)触发的更新,需要立即执行以保证用户交互的即时响应。
  • 连续输入优先级(InputContinuousLane):主要用于处理连续的用户输入,如滚动、拖拽等操作,响应速度要求较高,但允许有一定延迟。
  • 默认优先级(DefaultLane):用于处理一般的更新任务,如组件内部状态的正常更新,优先级相对较低。
  • 过渡优先级(TransitionLanes):用于处理过渡动画和一些不影响用户交互的更新,可在后台进行,优先级较低。例如使用 startTransition 包裹的更新:
import React, { useState, startTransition } from 'react';

function App() {
    const [inputValue, setInputValue] = useState('');
    const [list, setList] = useState([]);

    const handleInputChange = (e) => {
        const newInputValue = e.target.value;
        setInputValue(newInputValue);
        startTransition(() => {
            const newList = Array.from({ length: 1000 }, (_, i) => i + newInputValue);
            setList(newList);
        });
    };

    return (
        <div>
            <input
                type="text"
                value={inputValue}
                onChange={handleInputChange}
            />
            <ul>
                {list.map((item) => (
                    <li key={item}>{item}</li>
                ))}
            </ul>
        </div>
    );
}
  • 重试优先级(RetryLanes):当某个更新因为某些原因(如数据加载失败)需要重试时使用。
  • 空闲优先级(IdleLane):用于处理在空闲时间执行的更新,如一些非关键的 UI 渲染、数据预加载等。

3. 调度任务入队

当更新触发并分配优先级后,相关的更新任务会被加入到调度队列中。在 React 内部,每个 Fiber 节点都有一个更新队列,更新任务会被添加到对应的 Fiber 节点的更新队列中。同时,根节点(FiberRoot)会维护一个全局的调度列表,用于管理所有有待处理工作的根节点。

4. 微任务调度

为了高效地处理调度任务,React 会安排微任务来处理根节点的调度任务。具体过程如下:

  • 根节点入队:当一个根节点收到更新时,ensureRootIsScheduled 函数会被调用,它会确保该根节点被添加到调度列表中,并安排一个微任务来处理根节点的调度任务。
  • 微任务执行:微任务会调用 processRootScheduleInMicrotask 函数,该函数会遍历所有调度中的根节点,为每个根节点调用 scheduleTaskForRootDuringMicrotask 函数安排具体的任务。

5. 任务选择与执行

scheduleTaskForRootDuringMicrotask 函数中,会进行以下操作来选择和执行任务:

  • 检查过期任务:调用 markStarvedLanesAsExpired 函数检查是否有通道被其他工作阻塞,如果有,则将其标记为过期。
  • 确定下一个要处理的通道及其优先级:通过 getNextLanes 函数根据当前根节点的状态和 Lane 的优先级来决定下一个要处理的任务。
function getNextLanes(
    root: FiberRoot,
    wipLanes: Lanes,
    rootHasPendingCommit: boolean,
): Lanes {
    const pendingLanes = root.pendingLanes;
    if (pendingLanes === NoLanes) {
        return NoLanes;
    }
    const nonIdlePendingLanes = pendingLanes & NonIdleLanes;
    if (nonIdlePendingLanes !== NoLanes) {
        const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;
        if (nonIdleUnblockedLanes !== NoLanes) {
            nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);
        }
    }
    return nextLanes;
}
  • 安排回调任务:根据确定的优先级,将任务安排到对应的调度器中执行。如果是同步优先级的任务,会立即执行;对于其他优先级的任务,会根据调度器的规则进行时间切片处理,避免长时间阻塞主线程。

6. 协调与渲染

一旦任务被选中执行,React 会进入协调(Reconciliation)阶段,通过比较新旧虚拟 DOM 树的差异,找出需要更新的部分。这个过程会使用 diff 算法,根据节点的 keytype 等信息来判断节点是否可以复用,从而减少不必要的 DOM 操作。

协调完成后,React 会进入渲染阶段,将差异应用到实际的 DOM 上,完成界面的更新。

7. 中断与恢复

在调度过程中,如果出现更高优先级的任务,React 可能会中断当前正在执行的低优先级任务,先处理高优先级任务。等高优先级任务完成后,再恢复低优先级任务的执行。这一机制确保了用户交互等关键任务能够及时响应,提升了应用的响应性能。

综上所述,React 的调度过程是一个复杂而高效的机制,通过优先级分配、调度任务入队、微任务调度、任务选择与执行、协调与渲染以及中断与恢复等步骤,确保了 React 应用能够高效、稳定地运行。

Fiber上如何存储更新队列的

在 React 中,Fiber 节点用于表示组件树中的每个节点,它是 React 16.x 版本引入的新架构的核心数据结构。每个 Fiber 节点都会存储自己的更新队列,以下详细介绍 Fiber 上存储更新队列的相关内容。

1. 更新队列的结构

在 React 中,更新队列(UpdateQueue)是一个链表结构,每个更新(Update)对象代表一次状态或属性的更新操作。以下是简化的更新队列和更新对象的结构示例:

// 更新对象结构
const update = {
    // 更新的优先级
    lane: Lane, 
    // 更新的负载,例如 setState 传入的参数
    payload: any, 
    // 更新完成后的回调函数
    callback: Function | null, 
    // 指向下一个更新对象的引用
    next: Update | null 
};

// 更新队列结构
const updateQueue = {
    // 基本状态,通常是当前状态
    baseState: any, 
    // 第一个更新对象
    firstUpdate: Update | null, 
    // 最后一个更新对象
    lastUpdate: Update | null 
};

2. Fiber 节点上的更新队列属性

每个 Fiber 节点都有一个 updateQueue 属性,用于存储该节点的更新队列。在 Fiber 节点的定义中,会有类似如下的属性:

const fiber = {
    // ...其他属性
    updateQueue: {
        baseState: null,
        firstUpdate: null,
        lastUpdate: null
    },
    // ...其他属性
};

3. 更新入队操作

当调用 setState 或其他触发更新的方法时,会创建一个新的更新对象,并将其添加到对应的 Fiber 节点的更新队列中。以下是简化的入队操作示例:

function enqueueUpdate(fiber, update) {
    const updateQueue = fiber.updateQueue;
    if (updateQueue.lastUpdate === null) {
        // 如果更新队列为空,将新更新作为第一个和最后一个更新
        updateQueue.firstUpdate = update;
        updateQueue.lastUpdate = update;
    } else {
        // 否则,将新更新添加到队列末尾
        updateQueue.lastUpdate.next = update;
        updateQueue.lastUpdate = update;
    }
}

4. 处理更新队列

在调度过程中,React 会处理 Fiber 节点上的更新队列。具体步骤如下:

  • 遍历更新队列:从 firstUpdate 开始,依次处理每个更新对象。
  • 合并更新:根据更新对象的 payload,合并到 baseState 上,生成新的状态。
  • 执行回调:如果更新对象有 callback,则在更新完成后执行该回调。

以下是简化的处理更新队列的示例:

function processUpdateQueue(fiber) {
    const updateQueue = fiber.updateQueue;
    let baseState = updateQueue.baseState;
    let update = updateQueue.firstUpdate;

    while (update !== null) {
        const payload = update.payload;
        if (typeof payload === 'function') {
            // 如果 payload 是函数,调用该函数生成新状态
            baseState = payload(baseState);
        } else {
            // 否则,直接合并 payload 到 baseState
            baseState = { ...baseState, ...payload };
        }

        if (update.callback !== null) {
            // 执行回调
            update.callback();
        }

        update = update.next;
    }

    // 更新 Fiber 节点的状态
    fiber.memoizedState = baseState;
    // 清空更新队列
    updateQueue.firstUpdate = null;
    updateQueue.lastUpdate = null;
}

5. 实际应用中的更新队列

在实际的 React 应用中,更新队列的处理会更加复杂,会考虑优先级(Lane)、并发更新等因素。例如,不同优先级的更新可能会有不同的处理顺序,高优先级的更新会优先处理。

function enqueueUpdateWithLane(fiber, update, lane) {
    update.lane = lane;
    // 根据优先级插入更新队列,高优先级的更新可能会插入到队列头部
    const updateQueue = fiber.updateQueue;
    if (updateQueue.lastUpdate === null) {
        updateQueue.firstUpdate = update;
        updateQueue.lastUpdate = update;
    } else {
        // 这里可以根据 lane 进行更复杂的插入逻辑
        updateQueue.lastUpdate.next = update;
        updateQueue.lastUpdate = update;
    }
}

通过以上方式,Fiber 节点能够有效地存储和处理更新队列,确保 React 应用能够高效地进行状态更新和界面渲染。

React中的Lanes和Lane

在 React 的 Lane 模型里,laneslane 是紧密相关且用于表示任务优先级的重要概念,下面详细介绍它们的关系。

1. 类型定义与本质

  • lane
    • 在 React 源码里,lane 被定义为 number 类型。它本质上是一个用二进制数表示的单一优先级通道,每个二进制位代表一种特定的优先级。比如 SyncLane 常量,它对应一个特定的二进制数,代表同步更新这种优先级。
    export type Lane = number;
    export const SyncLane: Lane = 0b0000000000000000000000000000010;
    
  • lanes:同样被定义为 number 类型。lanes 表示一组 lane 的集合,它可以通过按位或(|)操作将多个 lane 组合在一起,从而代表多个优先级的集合。
    export type Lanes = number;
    const SyncUpdateLanes: Lanes = SyncLane | InputContinuousLane;
    

2. 逻辑关系

  • lanelanes 的基本组成单元lanes 由一个或多个 lane 组合而成。就像积木一样,每个 lane 是一块积木,lanes 则是由这些积木搭建起来的集合。例如,当一个组件同时有同步更新和连续输入更新时,对应的 lanes 就会包含 SyncLaneInputContinuousLane 这两个 lane
  • 操作关系:可以对 lanes 进行各种位运算操作,从而实现对其中 lane 的判断、添加、移除等操作。
    • 判断是否包含某个 lane:使用按位与(&)操作,如果结果不为 0,则表示 lanes 中包含该 lane
    function hasLane(lanes: Lanes, lane: Lane): boolean {
        return (lanes & lane) !== 0;
    }
    
    • 添加 lanelanes:使用按位或(|)操作。
    function addLane(lanes: Lanes, lane: Lane): Lanes {
        return lanes | lane;
    }
    
    • lanes 中移除 lane:使用按位与和按位取反(& ~)操作。
    function removeLane(lanes: Lanes, lane: Lane): Lanes {
        return lanes & ~lane;
    }
    

3. 使用场景差异

  • lane:通常在需要明确指定某个特定优先级时使用。例如,当你使用 startTransition 来标记一个过渡更新时,这个更新会被分配到过渡优先级对应的 lane 中。
    import { startTransition } from 'react';
    
    startTransition(() => {
        // 这里的更新会被分配到过渡优先级的 lane
        setSomeState(newValue);
    });
    
  • lanes:在调度器进行任务调度和优先级管理时,更多地使用 lanes。调度器需要考虑多个任务的优先级集合,根据 lanes 来决定先处理哪些任务。例如,getNextLanes 函数会根据当前的 lanes 情况,找出下一个要处理的任务对应的 lanes
    function getNextLanes(
        root: FiberRoot,
        wipLanes: Lanes,
        rootHasPendingCommit: boolean
    ): Lanes {
        // 根据各种条件计算并返回下一个要处理的 lanes
    }
    

综上所述,lane 是单一优先级的表示,而 lanes 是多个 lane 组成的集合,它们共同构成了 React 中灵活且高效的任务优先级管理体系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值