梳理React中的fiber架构

文章目录

    • 产生背景
    • 核心概念
    • 工作原理
    • 工作流程
    • 优势特点

产生背景

在React16之前使用的虚拟DOM是数组的形式,又因为React本身是应用级框架,状态改变后并不能准确知道是哪个组件发生了改变,只能对整个应用进行diff协调,受限于虚拟DOM的结构,这个过程不能被打断,于是就会出现JS占用主线程过多时间,导致页面不能及时渲染而出现卡顿。
于是FaceBook团队对内部机制进行重构,旨在解决 React 在处理复杂和大型应用时面临的一些性能问题,尤其是渲染和更新界面的过程,fiber就诞生了。

核心概念

Fiber数据结构:Fiber本质上是一个JavaScript对象,代表React的一个工作单元,包含组件相关的信息,如类型、属性、状态等。

//本质上是一个对象结构
{type: 'div',  // 组件类型key: null,   // React keyprops: { ... }, // 输入的propsstate: { ... }, // 组件的state (如果是class组件或带有state的function组件)child: Fiber | null,  // 第一个子元素的Fibersibling: Fiber | null,  // 下一个兄弟元素的Fiberreturn: Fiber | null,  // 父元素的Fiber// ...其他属性
}

Fiber树:Fiber树是React Fiber架构中的一个核心概念,它是一种特殊的链表数据结构,用于表示React组件树的结构和状态。Fiber树的每个节点(Fiber节点)代表React中的一个工作单元,包含了组件的相关信息,如类型、属性、状态等,每个节点通过child、sibling和return字段构成一个链表结构,使React能够遍历组件树并知道从哪里开始、继续或停止工作,从而形成Fiber树。

//FiberNode结构
function FiberNode(this: $FlowFixMe,tag: WorkTag,pendingProps: mixed,key: null | string,mode: TypeOfMode,
) {// 基本属性this.tag = tag; // 描述此Fiber的启动模式的值(LegacyRoot = 0; ConcurrentRoot = 1)this.key = key; // React keythis.elementType = null; // 描述React元素的类型。例如,对于JSX<App />,elementType是Appthis.type = null; // 组件类型this.stateNode = null; // 对于类组件,这是类的实例;对于DOM元素,它是对应的DOM节点。// Fiber链接this.return = null; // 指向父Fiberthis.child = null; // 指向第一个子Fiberthis.sibling = null; // 指向其兄弟Fiberthis.index = 0; // 子Fiber中的索引位置this.ref = null; // 如果组件上有ref属性,则该属性指向它this.refCleanup = null; // 如果组件上的ref属性在更新中被删除或更改,此字段会用于追踪需要清理的旧ref// Props & Statethis.pendingProps = pendingProps; // 正在等待处理的新propsthis.memoizedProps = null; // 上一次渲染时的propsthis.updateQueue = null; // 一个队列,包含了该Fiber上的状态更新和副作用this.memoizedState = null; // 上一次渲染时的statethis.dependencies = null; // 该Fiber订阅的上下文或其他资源的描述// 工作模式this.mode = mode; // 描述Fiber工作模式的标志(例如Concurrent模式、Blocking模式等)。// Effectsthis.flags = NoFlags; // 描述该Fiber发生的副作用的标志(十六进制的标识)this.subtreeFlags = NoFlags; // 描述该Fiber子树中发生的副作用的标志(十六进制的标识)this.deletions = null; // 在commit阶段要删除的子Fiber数组this.lanes = NoLanes; // 与React的并发模式有关的调度概念。this.childLanes = NoLanes; // 与React的并发模式有关的调度概念。this.alternate = null; // Current Tree和Work-in-progress (WIP) Tree的互相指向对方tree里的对应单元// 如果启用了性能分析if (enableProfilerTimer) {// ……}// 开发模式中if (__DEV__) {// ……}
}

工作单元与任务调度:每个Fiber节点代表一个工作单元,React通过任务调度器根据任务的优先级来决定执行顺序,实现可中断和恢复的任务调度。

任务调度的实现:
调度主要涉及两个核心概念,优先级控制权

  1. 每个任务在调和的过程中,有自己的优先级,react之前的版本用expirationTime属性代表优先级,该优先级和IO不能很好的搭配工作(io的优先级高于cpu的优先级),现在有了更加细粒度的优先级表示方法Lane,Lane用二进制位表示优先级,二进制中的1表示位置,同一个二进制数可以有多个相同优先级的位,这就可以表示‘批’的概念,而且二进制方便计算。
  2. 在构建和更新用户界面时,React Fiber 会分批处理任务,并在每批任务之间让出控制权,以便浏览器可以执行其他操作。在 React
    中,让出控制权的技术通常是通过使用浏览器的 requestIdleCallback 函数实现的,但是 react
    考虑到兼容性问题并没有使用该 API,而是自己实现了一套机制,通过MessageChannel +
    requestAnimationFrame
    自己模拟实现了requestIdleCallback。
  3. 具体过程:Scheduer 中通过 performance.now() 来获取 当前时间,在产生的一个任务对象中会记录该任务的开始时间(这个开始时间会根据任务优先级的不同添加不同的delay)和过期时间(这个过期时间是开始时间+timeout,不同的优先级有不同的timeout,过期时间小于开始时间时说明是立即执行),整个任务流水线在开始执行时也会记录一个全局的开始时间(每次任务中断后,在再次执行时,相当于开启了一个新的任务流水线,所以这个全局的开始时间会进行更新),当任务到了过期时间,并且没有到达需要浏览器渲染的时候就执行任务。

为什么不是setTimeout?
因为setTimeout的递归层级过深的话,延迟就不是1ms,而是4ms,这样会造成延迟时间过长
为什么不是requestAnimationFrame?
requestAnimationFrame是在微任务执行完之后,浏览器重排重绘之前执行,执行的时机是不准确的。如果raf之前JS的执行时间过长,依然会造成延迟。
与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。(如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次)
为什么不是requestIdleCallback?
requestIdleCallback的执行时机是在浏览器重排重绘之后,也就是浏览器的空闲时间执行。其实执行的时机依然是不准确的。
为什么是 MessageChannel?
首先,MessageChannel的执行时机比setTimeout靠前。其次,requestIdleCallback并不是所有浏览器都支持的。为了解决这个问题,React采用MessageChannel来模拟requestIdleCallback。
如何
如何判断有没有到达需要浏览器渲染的时候?
通过 shouldYieldToHost 方法来判断是否应该暂停任务流水线,归还主线程来进行渲染操作,shouldYieldToHost 中会获取当前时间,并减去全局的开始时间,如果这个差值大于了 Scheduer 设置的临界值(5ms),说明任务流水线执行时间有点长了,需要归还主线程,于是 shouldYieldToHost 返回 true。

工作原理

1.单元工作:每个Fiber节点代表一个单元,所有Fiber节点共同组成一个Fiber链表树(有链接属性,同时又有树的结构),这种结构让React可以细粒度控制节点的行为。
2.链接属性:child、sibling和return字段构成了Fiber之间的链接关系,使React能够遍历组件树并知道从哪里开始、继续或停止工作。
child指向子节点,sibing指向兄弟节点,return指向兄弟节点。
fiber结构
注:遍历顺序为ABCED

为什么选择链表?
Fiber 采用链表数据结构的原因是因为链表可以方便地在列表的中间插入和删除元素。这在构建和更新用户界面时非常有用,因为可能会有大量的元素需要插入或删除。

3.双缓冲技术:React在更新时,会根据现有的Fiber树(Current Tree)创建一个新的临时树(Work-in-progress (WIP) Tree),WIP-Tree包含了当前更新受影响的最高节点直至其所有子孙节点。Current Tree是当前显示在页面上的视图,WIP-Tree则是在后台进行更新,WIP-Tree更新完成后会复制其它节点,并最终替换掉Current Tree,成为新的Current Tree。因为React在更新时总是维护了两个Fiber树,所以可以随时进行比较、中断或恢复等操作,而且这种机制让React能够同时具备优秀的渲染性能和UI的稳定性。

Reconciler中在进行虚拟DOM的diff,它之所以可以中断,是因为fiber链表的这种结构,我们只要保存当前任务的指针就可以在下次直接找到该任务并执行,所以这种架构天然支持中断,并且 Fiber 双缓存 构建的两颗树中的 wip Fiber Tree 中保存着已经构建的虚拟DOM,当中断继续运行时根据保存的指针找到未完成的任务继续进行协调构建 wip Fiber Tree。

双缓存

4.State和Props
props是组件的属性,是父组件传递给子组件的数据。在Fiber中,props被存储在Fiber节点的memoizedProps和pendingProps字段中。

  • memoizedProps:表示上一次渲染时的props。这是在组件完成渲染后保存的props的副本。
  • pendingProps:表示正在等待处理的新props。当组件接收到新的props时,这些props会被存储在pendingProps中,等待下一次渲染。

React通过比较memoizedProps和pendingProps来确定组件的props是否发生了变化。如果发生变化,React会触发组件的更新。

state是组件的内部状态,通常由组件自身管理。在Fiber中,state被存储在Fiber节点的memoizedState字段中。

  • memoizedState:表示上一次渲染时的state。这是在组件完成渲染后保存的state的副本。

5.副作用的追踪:副作用的追踪是一个关键机制,它允许React在渲染过程中收集和管理需要执行的副作用操作。这些副作用操作包括DOM更新、生命周期方法调用等。

  1. 标记副作用:

    每个Fiber节点都有一个flags字段和一个subtreeFlags字段,用于标识该节点及其子树中需要执行的副作用。flags字段直接标记该Fiber节点的副作用类型,而subtreeFlags字段则用于标记其子树中所有Fiber节点的副作用类型。这些副作用类型包括但不限于:

Placement:表示需要将一个DOM节点插入到DOM树中。
Update:表示需要更新一个DOM节点。
Deletion:表示需要从DOM树中删除一个DOM节点。

React 的所有 effect 类型都在这里 packages/shared/ReactSideEffectTags.js。

2.构建副作用链表

在React的渲染流程中,render阶段会从根节点开始处理所有的Fiber节点,收集有副作用的Fiber节点,并构建副作用链表。这个链表是通过Fiber节点的nextEffect指针连接而成的。在completeUnitOfWork阶段,每个Fiber节点会将自己的副作用链表提交给其父节点。具体步骤如下:

  • 提交副作用链表:如果父节点没有副作用链表,则将父节点的firstEffect指向当前节点的firstEffect。如果当前节点有副作用链表,则将父节点的lastEffect的nextEffect指向当前节点的firstEffect。
  • 添加当前节点到父节点的副作用链表:如果当前节点本身有副作用(即flags > 1),则将当前节点添加到父节点的副作用链表中。

3.执行副作用
在commit阶段,React会遍历副作用链表,并根据每个Fiber节点的flags标志执行对应的副作用操作。这个阶段是不可中断的,以确保所有副作用要么全部提交,要么全部不提交。具体操作包括:

  • Placement:调用commitPlacement执行实际的插入操作,并清除Placement标记。
  • Update:调用commitWork执行实际的更新操作,并清除Update标记。
  • Deletion:调用commitDeletion执行实际的删除操作,并清除Deletion标记。

4.使用位操作
React使用位操作来处理副作用标记,因为位操作可以高效地进行集合操作。例如,通过按位或操作(|),可以将多个副作用标记合并到一个字段中;通过按位与操作(&),可以检查一个Fiber节点是否具有特定的副作用标记。

注:这也是Fiber架构更快的原因之一

工作流程

React Fiber的工作流程主要分为两个阶段:Reconciliation(调和)Commit(提交)

调和
调和的主要目标是在构建工作树的阶段,通过比较新的props和旧的Fiber树来确定哪些部分需要更新。
分三个阶段:

  1. beginWork:创建和标记更新节点

    1.1判断节点是否要更新

// packages/react-reconciler/src/ReactFiberBeginWork.js
function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime,
): Fiber | null {switch (workInProgress.tag) {case ClassComponent: {return updateClassComponent(current, workInProgress, Component, resolvedProps);}}
}function updateClassComponent(current: Fiber | null, workInProgress: Fiber, Component: any, nextProps) {const nextUnitOfWork = finishClassComponent(current, workInProgress, Component, shouldUpdate);return nextUnitOfWork;
}
function finishClassComponent(current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean
) {return workInProgress.child; 
}

beginWork 本身对递归没什么实际进展,主要是根据 tag 分发逻辑。

1.2 判断节点更新还是复用

function updateClassComponent(current, workInProgress, Component, nextProps) {// 确定组件是否应该更新const shouldUpdate = determineIfComponentShouldUpdate(current,workInProgress,nextProps);// 传递正确的 shouldUpdate 参数const nextUnitOfWork = finishClassComponent(current,workInProgress,Component,shouldUpdate,false // 假设没有 context);return nextUnitOfWork;
}function finishClassComponent(current,workInProgress,Component,shouldUpdate,hasContext
) {// 如果不需要更新,则复用当前子树if (!shouldUpdate) {cloneChildFibers(current, workInProgress);return workInProgress.child;}// 继续正常的更新流程return workInProgress.child;
}function determineIfComponentShouldUpdate(current, workInProgress, nextProps) {// 首次渲染时总是更新if (current === null) {return true;}// 浅比较 props 判断是否需要更新const prevProps = current.memoizedProps;// 检查 props 数量是否相同if (Object.keys(prevProps).length !== Object.keys(nextProps).length) {return true;}// 检查每个 prop 是否相同for (let key in prevProps) {if (prevProps[key] !== nextProps[key]) {// 特殊处理 childrenif (key === 'children') {// 简化的 children 比较,实际可能需要更复杂的比较if (prevProps.children !== nextProps.children) {return true;}} else {return true;}}}return false;
}// 辅助函数:复用当前子树结构
function cloneChildFibers(current, workInProgress) {// 简化实现,实际 React 中会更复杂if (current.child) {workInProgress.child = {...current.child,alternate: current.child,return: workInProgress};// 递归克隆所有子节点cloneChildren(current.child, workInProgress.child);}
}function cloneChildren(currentParent, wipParent) {let currentChild = currentParent.child;let wipChild = null;let prevWipChild = null;while (currentChild) {const newChild = {...currentChild,alternate: currentChild,return: wipParent,sibling: null};if (!wipChild) {wipParent.child = newChild;wipChild = newChild;} else {prevWipChild.sibling = newChild;}prevWipChild = newChild;currentChild = currentChild.sibling;}
}

2.completeUnitOfWork和completeWork:收集副作用列表
2.1completeUnitOfWork 负责遍历Fiber节点,同时记录了有副作用节点的关系

// packages/react-reconciler/src/ReactFiberWorkLoop.js
function completeUnitOfWork(unitOfWork: Fiber): void {let completedWork: Fiber = unitOfWork; // 当前正在完成的工作单元do {const current = completedWork.alternate; // 当前Fiber节点在另一棵树上的版本const returnFiber = completedWork.return; // 当前Fiber节点的父节点let next;next = completeWork(current, completedWork, renderLanes); // 调用completeWork函数if (next !== null) {// 当前Fiber还有工作要完成workInProgress = next;return;}const siblingFiber = completedWork.sibling;if (siblingFiber !== null) {// 如果有兄弟节点,则进入兄弟节点的工作workInProgress = siblingFiber;return;}// 如果没有兄弟节点,回到父节点继续completedWork = returnFiber;workInProgress = completedWork;} while (completedWork !== null);// 如果处理了整个Fiber树,更新workInProgressRootExitStatus为RootCompleted,表示调和已完成if (workInProgressRootExitStatus === RootInProgress) {workInProgressRootExitStatus = RootCompleted;} 
}

需要注意的是,next 指针不应该重复经过同一个节点。因为如果向下的过程中经过某个节点,在向上的过程中又出现,就会再次进入 beginWork,造成死循环。
completeUnitOfWork 内部又创建了一层循环,搭配一个向上的新指针 workInProgress,然后循环看当前指针节点,有兄弟节点就返回交还给外层循环,没有就向上到父节点,直到最上面的根节点。

2.2 completeWork 在 completeUnitOfWork 中被调用,主要是根据 tag 进行不同的处理。

// packages/react-reconciler/src/ReactFiberCompleteWork.js
function completeWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,
): Fiber | null {const newProps = workInProgress.pendingProps;switch (workInProgress.tag) {// 多种tagcase FunctionComponent:case ForwardRef:case SimpleMemoComponent:bubbleProperties(workInProgress)return null;case ClassComponent:// 省略逻辑// ……bubbleProperties(workInProgress)return null;case HostComponent:// 省略逻辑// ……return null;// 多种tag// ……}
}

从源码中可以看出来,completeWork起到一个分支处理的作用,在分支里,调用了bubbleProperties函数,这个函数主要用于记录Fiber的副作用标志为子Fiber创建链表

// packages/react-reconciler/src/ReactFiberCompleteWork.jsfunction bubbleProperties(completedWork: Fiber) {const didBailout =completedWork.alternate !== null &&completedWork.alternate.child === completedWork.child; // 当前的Fiber与其alternate(备用/上一次的Fiber)有相同的子节点,则跳过更新let newChildLanes = NoLanes; // 合并后的子Fiber的laneslet subtreeFlags = NoFlags; // 子树的flags。if (!didBailout) {// 没有bailout,需要冒泡子Fiber的属性到父Fiberlet child = completedWork.child;// 遍历子Fiber,并合并它们的lanes和flagswhile (child !== null) {newChildLanes = mergeLanes(newChildLanes,mergeLanes(child.lanes, child.childLanes),);subtreeFlags |= child.subtreeFlags;subtreeFlags |= child.flags;child.return = completedWork; // Fiber的return指向父Fiber,确保整个Fiber树的一致性child = child.sibling;}completedWork.subtreeFlags |= subtreeFlags; // 合并所有flags(副作用)} else {// 有bailout,只冒泡那些具有“静态”生命周期的flagslet child = completedWork.child;while (child !== null) {newChildLanes = mergeLanes(newChildLanes,mergeLanes(child.lanes, child.childLanes),);subtreeFlags |= child.subtreeFlags & StaticMask; // 不同subtreeFlags |= child.flags & StaticMask; // 不同child.return = completedWork;child = child.sibling;}completedWork.subtreeFlags |= subtreeFlags;}completedWork.childLanes = newChildLanes; // 获取所有子Fiber的lanes。return didBailout;
}

调和过程可以被中断,那么在源码中是怎么实现的呢?

整体流程:
1.上下文准备:保存当前执行上下文和 dispatcher,设置渲染上下文
2.工作进度检查:判断是否需要为新渲染任务准备新的工作进度树
3.并发工作循环:持续处理工作单元,直到完成或被挂起
4.挂起处理:根据不同挂起原因(数据、错误、实例等)执行不同恢复策略
5.上下文恢复:重置执行上下文和 dispatcher
6.状态返回:根据渲染完成情况返回相应状态

// packages/react-reconciler/src/ReactFiberWorkLoop.js
// 以下只是核心逻辑的代码,不是renderRootConcurrent的完整源码
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {// 保存当前的执行上下文和 dispatcherconst prevExecutionContext = executionContext;executionContext |= RenderContext;const prevDispatcher = pushDispatcher(root.containerInfo);const prevCacheDispatcher = pushCacheDispatcher();if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {// 如果当前的工作进度树与传入的 root 或 lanes 不匹配,我们需要为新的渲染任务准备一个新的堆栈。// ……}// 持续的工作循环,除非中断发生,否则会一直尝试完成渲染工作outer: do {try {if (workInProgressSuspendedReason !== NotSuspended &&workInProgress !== null) {// 如果当前的工作进度是由于某种原因而被挂起的,并且仍然有工作待处理,那么会处理它const unitOfWork = workInProgress;const thrownValue = workInProgressThrownValue;// 根据不同挂起原因,进行中断、恢复等计算resumeOrUnwind: switch (workInProgressSuspendedReason) {case SuspendedOnError: {// 如果工作因错误被挂起,那么工作会被中断,并从最后一个已知的稳定点继续// ……省略逻辑break;}case SuspendedOnData: {// 工作因等待数据(通常是一个异步请求的结果)而被挂起,// ……省略逻辑break outer;}case SuspendedOnInstance: {// 将挂起的原因更新为SuspendedOnInstanceAndReadyToContinue并中断工作循环,标记为稍后准备好继续执行workInProgressSuspendedReason = SuspendedOnInstanceAndReadyToContinue;break outer;}case SuspendedAndReadyToContinue: {// 表示之前的挂起工作现在已经准备好继续执行if (isThenableResolved(thenable)) {// 如果已解析,这意味着需要的数据现在已经可用workInProgressSuspendedReason = NotSuspended;workInProgressThrownValue = null;replaySuspendedUnitOfWork(unitOfWork); // 恢复执行被挂起的工作} else {workInProgressSuspendedReason = NotSuspended;workInProgressThrownValue = null;throwAndUnwindWorkLoop(unitOfWork, thrownValue); // 继续循环}break;}case SuspendedOnInstanceAndReadyToContinue: {// ……省略部分逻辑const isReady = preloadInstance(type, props);if (isReady) {// 实例已经准备好workInProgressSuspendedReason = NotSuspended; // 该fiber已完成,不需要再挂起workInProgressThrownValue = null;const sibling = hostFiber.sibling;if (sibling !== null) {workInProgress = sibling; // 有兄弟节点,开始处理兄弟节点} else {// 没有兄弟节点,回到父节点const returnFiber = hostFiber.return;if (returnFiber !== null) {workInProgress = returnFiber;completeUnitOfWork(returnFiber); // 收集副作用,前面有详细介绍} else {workInProgress = null;}}break resumeOrUnwind;}}// 还有其它case}}workLoopConcurrent(); // 如果没有任何工作被挂起,那么就会继续处理工作循环。break;} catch (thrownValue) {handleThrow(root, thrownValue);}} while (true);// 重置了之前保存的执行上下文和dispatcher,确保后续的代码不会受到这个函数的影响resetContextDependencies();popDispatcher(prevDispatcher);popCacheDispatcher(prevCacheDispatcher);executionContext = prevExecutionContext;// 检查调和是否已完成if (workInProgress !== null) {// 未完成return RootInProgress; // 返回一个状态值,表示还有未完成} else {// 已完成workInProgressRoot = null; // 重置rootworkInProgressRootRenderLanes = NoLanes; // 重置LanefinishQueueingConcurrentUpdates(); // 处理队列中的并发更新return workInProgressRootExitStatus; // 返回当前渲染root的最终退出状态}
}

在这里插入图片描述

提交

提交是遍历在Reconciliation阶段创建的副作用列表进行更新DOM并执行任何副作用。

主要分三个阶段:

  1. BeforeMutation:遍历副作用列表
// packages/react-reconciler/src/ReactFiberCommitWork.js
export function commitBeforeMutationEffects(root: FiberRoot,firstChild: Fiber,
): boolean {nextEffect = firstChild; // nextEffect是遍历此链表时的当前fibercommitBeforeMutationEffects_begin(); // 遍历fiber,处理节点删除和确认节点在before mutation阶段是否有要处理的副作用const shouldFire = shouldFireAfterActiveInstanceBlur; // 当一个焦点元素被删除或隐藏时,它会被设置为 trueshouldFireAfterActiveInstanceBlur = false;focusedInstanceHandle = null;return shouldFire;
}

这个没什么可说的,就是对副作用进行遍历,调用 getSnapshotBeforeUpdate 等生命周期方法。

  1. CommitMutation:正式提交
// packages/react-reconciler/src/ReactFiberCommitWork.jsexport function commitMutationEffects(root: FiberRoot,finishedWork: Fiber,committedLanes: Lanes,
) {// lanes和root被设置为"in progress"状态,表示它们正在被处理inProgressLanes = committedLanes;inProgressRoot = root;// 递归遍历Fiber,更新副作用节点commitMutationEffectsOnFiber(finishedWork, root, committedLanes);// 重置进行中的lanes和rootinProgressLanes = null;inProgressRoot = null;
}

commitMutationEffects 函数是 React 提交阶段的入口点,主要执行以下操作:

  • 状态准备:设置全局变量标记当前正在处理的 root 和 lanes
  • 副作用执行:递归遍历 Fiber 树,执行所有需要提交的副作用(DOM 更新、生命周期调用等)
  • 状态重置:清除全局标记,完成提交阶段

3.commitLayout:处理layout effects

// packages/react-reconciler/src/ReactFiberCommitWork.js
export function commitLayoutEffects(finishedWork: Fiber,root: FiberRoot,committedLanes: Lanes,
): void {inProgressLanes = committedLanes;inProgressRoot = root;// 创建一个current指向就Fiber树的alternateconst current = finishedWork.alternate;// 处理那些由useLayoutEffect创建的layout effectscommitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);inProgressLanes = null;inProgressRoot = null;
}

提交阶段三个子阶段的核心区别

阶段执行时机核心操作示例操作
Before MutationDOM 更新读取 DOM 状态(如 scroll 位置)getSnapshotBeforeUpdate、调度 useEffect
MutationDOM 更新执行 DOM 插入 / 更新 / 删除操作commitPlacementcommitUpdatecommitDeletion
LayoutDOM 更新,渲染同步执行依赖 DOM 的副作用、调用生命周期方法componentDidMountcomponentDidUpdateuseLayoutEffect 的回调函数

提交之后就无法中断了。

优势特点

提升性能:通过可中断的渲染和增量渲染,避免了长时间占用主线程资源,减少了卡顿现象,提高了应用的性能。
优化用户体验:能够根据任务优先级进行调度,确保高优先级任务得到及时处理,提升了应用的交互体验。
支持新特性:为React引入一些新特性提供了基础,如异步渲染、懒加载等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/86628.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/86628.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Modbus 数据模型:线圈、寄存器与功能码详解(二)

三、Modbus 功能码详解 3.1 功能码分类与作用 Modbus 功能码是 Modbus 通信协议中的关键组成部分&#xff0c;它如同一个 “指令指挥官”&#xff0c;在通信事务处理中扮演着核心角色。功能码占用 1 个字节的空间&#xff0c;取值范围为 1 到 255 &#xff08;0x01 - 0xFF&am…

多表连接查询:语法、注意事项与最佳实践

&#x1f517; 多表连接查询&#xff1a;语法、注意事项与最佳实践 多表连接是 SQL 的核心能力&#xff0c;用于关联多个表的数据。以下是深度解析&#xff0c;涵盖语法规范、性能陷阱及实战技巧&#xff1a; &#x1f4dc; 一、多表连接语法大全 1. 显式连接&#xff08;推荐…

使用Calibre对GDS进行数据遍历

在芯片的GDS数据里&#xff0c;使用Calibre对数据进行处理是非常常见的操作&#xff0c;但是GDS是一种和常规设计结构不太一样的一种数据&#xff0c;这里&#xff0c;通过这个小小的科普文章&#xff0c;一起看看怎么样在GDS里边做数据漫游吧&#xff01;闲言少叙&#xff0c;…

PyQtNode Editor 第二篇自定义可视化视图

在第一篇博客中,我们已经完成了 PyQtNode Editor 的基础环境搭建,并深入解析了自定义图形场景QDMGraphicsScene的实现原理。那个带有网格背景的场景就像一张空白的图纸,现在我们要在这张图纸上开始绘制真正的节点系统。 今天我们将聚焦于节点编辑器的核心数据结构设计,实现…

【扩欧应用】同余方程

与扩欧的联系 在同余方程的求解过程中&#xff0c;我们通常需要将方程转化为线性不定方程&#xff08;Diophantine 方程&#xff09;的形式&#xff0c;然后使用扩展欧几里得算法&#xff08;Extended Euclidean Algorithm, EEA&#xff09;求解。 同余方程是怎么转化为线性不…

结构化数据:NumPy 的结构化数组

文章目录 结构化数据&#xff1a;NumPy 的结构化数组探索结构化数组的创建更高级的复合类型记录数组&#xff1a;结构化数组的变体走向 Pandas 结构化数据&#xff1a;NumPy 的结构化数组 虽然我们的数据通常可以用同质数组很好地表示&#xff0c;但有时情况并非如此。本文将演…

phpcms 更换新域名更新栏目url和内容页url无法更新解决方法

更换域名后更新栏目url和内容页url还是无法更新为新的域名&#xff0c;手动把cache文件夹下能清除的缓存文件清除了还是不行&#xff0c;把数据库的缓存表内容清空了还是不行&#xff0c;问题在于栏目缓存并没有清除。 解决办法: (1)、找到文件&#xff1a;/caches/configs/sys…

玛哈特七辊矫平机:板材平整的精密卫士

在金属板材加工领域&#xff0c;表面平整度是衡量产品质量的核心指标之一。无论是汽车覆盖件、精密仪器外壳&#xff0c;还是建筑装饰板材&#xff0c;任何弯曲、波浪或翘曲都将严重影响后续加工精度、产品强度及美观度。七辊矫平机&#xff0c;凭借其独特的辊系结构设计&#…

融合聚类与分类的退役锂电智能分选技术:助力新能源汽车产业可持续发展

融合聚类与分类的退役锂电智能分选技术&#xff1a;助力新能源汽车产业可持续发展 关键词&#xff1a;退役锂离子电池分选 | 聚类分类融合 | 电化学阻抗谱(EIS) | 动态时间规整(DTW) | 多模态分类模型 新能源汽车 | 电池梯次利用 | 增量学习 | 数字孪生 | 联邦学习 | 双流特征…

jenkins中执行python脚本导入路径错误

&#x1f9fe; 问题一&#xff1a;ModuleNotFoundError: No module named jenkins &#x1f50d; 现象&#xff1a; 在本地运行正常&#xff0c;但在 Jenkins 中运行脚本时报错&#xff0c;提示找不到 jenkins 模块。 ❓ 原因分析&#xff1a; Python 默认只从当前目录或已…

华为云Flexus+DeepSeek征文 | 华为云ModelArts Studio实战指南:创建高效的AingDesk知识库问答助手

华为云FlexusDeepSeek征文 | 华为云ModelArts Studio实战指南&#xff1a;创建高效的AingDesk知识库问答助手 前言一、ModelArts Studio介绍1. 华为云ModelArts Studio简介2. 华为云ModelArts Studio主要特点3. 华为云ModelArts Studio主要使用场景 二、AingDesk介绍1. AingDes…

NLP基础1_word-embedding

基于github项目&#xff1a;https://github.com/shibing624/nlp-tutorial/tree/main 自然语言处理任务 1) 简单任务 拼写检查 Spell Checking 关键词检索 Keyword Search 同义词查找 Finding Synonyms 2) 中级任务 解析来自网站、文档等的信息 3) 复杂任务 机器翻译 Ma…

ClickHouse系列--BalancedClickhouseDataSource实现

clickhouse-jdbc中负载均衡数据源的实现。 基本逻辑如下&#xff1a; 1.通过配置的url串&#xff0c;来切分构造url列表&#xff1b; 2.通过一个定时线程任务&#xff0c;来不断的去ping url列表&#xff0c;来更新可用的url列表&#xff1b; 3.在可用列表中随机返回一个可用ur…

Linux目录说明

Linux Filesystem Hierarchy Standard&#xff08;FHS&#xff09; 1. /bin 全称&#xff1a;Binary&#xff08;二进制文件&#xff09;功能&#xff1a;存放系统最基础的可执行命令&#xff0c;所有用户&#xff08;包括普通用户&#xff09;都能使用&#xff0c;用于系统启…

鸿蒙 Grid 与 GridItem 深度解析:二维网格布局解决方案

一、引言&#xff1a;网格布局 —— 多维度数据展示的黄金方案 在鸿蒙应用开发体系中&#xff0c;网格布局作为处理多元素有序排列的核心方案&#xff0c;广泛应用于电商商品陈列、图片画廊、功能矩阵等场景。鸿蒙提供的 Grid 与 GridItem 组件通过声明式语法构建灵活的二维布…

​​Vue 开发环境配置:使用 devServer.proxy 解决跨域问题​-vue中文件vue.config,js中配置devserver做反向代理到后端

​​Vue 开发环境配置&#xff1a;使用 devServer.proxy 解决跨域问题​​ ​​引言​​ 在现代 Web 开发中&#xff0c;前端和后端通常独立开发&#xff0c;前端运行在 http://localhost:8080&#xff0c;而后端可能运行在 http://localhost:8000 或其他端口。由于浏览器的 …

JVM 中的 GC 算法演进之路!(Serial、CMS、G1 到 ZGC)

引言 想象一下&#xff0c;Java 程序运行就像在一个巨大的图书馆里借书还书。这个图书馆&#xff08;JVM 的内存堆区&#xff09;为了高效运转&#xff0c;需要一个聪明的“图书管理员”来清理失效的书籍&#xff08;垃圾对象&#xff09;。这&#xff0c;就是垃圾回收器&#…

(9)python+playwright自动化测试-页面(page)

1.简介 通过前边的讲解和学习&#xff0c;细心认真地你可能发现在Playwright中&#xff0c;没有Element这个概念&#xff0c;只有Page的概念&#xff0c;Page不仅仅指的是某个页面&#xff0c;例如页面间的跳转等&#xff0c;还包含了所有元素、事件的概念&#xff0c;所以我们…

《自动控制原理 》- 第 1 章 自动控制的基本原理与方式

1-1 自动控制的基本原理与方式 自动控制是指在没有人直接参与的情况下&#xff0c;利用外加的设备或装置&#xff0c;使机器、设备或生产过程的某个工作状态或参数按照预定的规律运行。自动控制的核心原理是反馈控制&#xff0c;即通过将系统的输出量回送到输入端&#xff0c;与…

DL00715-基于YOLOv11的水面漂浮物目标检测含数据集

【论文必备】基于YOLOv11的水面漂浮物目标检测——让你的研究走在科技前沿&#xff01; 在环境监测、海洋保护和水质管理领域&#xff0c;水面漂浮物的检测一直是一个亟待解决的难题。传统的人工巡检方式不仅耗时费力&#xff0c;还无法覆盖广泛的水域范围。如今&#xff0c;基…