前言
React是一个用于构建界面的JavaScript库。它的外围是跟踪组件状态变动并将更新后的状态更新到屏幕上。在React中,咱们把这个过程称为 reconciliation (协调)。通过调用setState办法,React查看状态或属性是否已更改,并在UI层上更新。
背景介绍
首先看一个简略的例子:
class ClickCounter extends React.Component { constructor(props) { super(props); this.state = {count: 0}; this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState((state) => { return {count: state.count + 1}; }); } render() { return [ <button key="1" onClick={this.handleClick}>Update counter</button>, <span key="2">{this.state.count}</span> ] }}
这是一个简略的计数器的例子, 点击按钮,组件的状态就会在处理程序中更新,组件的状态的更新反过来会引起span元素的内容更新。
上面是在协调阶段,React外部会有各种流动。以下是计数器在协调阶段所做的操作:
- 更新state的count属性
- 查看并比拟ClickCounter的子代以及props
- 更新span元素
协调期间还会执行其余流动,例如调用生命周期办法,更新ref。这些流动在Fiber架构中统称为"work"(工作)。work的类型取决于React元素的类型。React元素有多种类型,比方类组件,函数组件,Portals,DOM节点等。而React元素类型则是由React.createElement的第一个参数决定。React.createElement函数在render创立元素中调用。
React.createElement到Fiber
JSX编译
<button key="1" onClick={this.onClick}>Update counter</button><span key="2">{this.state.count}</span>
JSX在通过编译后,会失去如下的后果。这是render办法真正返回的后果
class ClickCounter { ... render() { return [ React.createElement( 'button', { key: '1', onClick: this.onClick }, 'Update counter' ), React.createElement( 'span', { key: '2' }, this.state.count ) ] }}
React.createElement函数会返回如下的后果:
[ { $$typeof: Symbol(react.element), type: 'button', key: "1", props: { children: 'Update counter', onClick: () => { ... } } }, { $$typeof: Symbol(react.element), type: 'span', key: "2", props: { children: 0 } }]
- $$typeof属性,用于将它们标示为react元素
- type,key,props,用于形容元素
而对于组件<ClickCounter>
的元素,它没有props和key:
{ $$typeof: Symbol(react.element), key: null, props: {}, ref: null, type: ClickCounter}
Fiber节点
在协调期间,render办法返回的React元素会被合并到Fiber节点树之中。每一个React元素都有对应的Fiber节点。与React元素不同,Fiber不会在每一次渲染的时候从新创立。Fiber会保留组件的状态和DOM。
后面探讨过,依据不同的React元素类型,会执行不同的流动。例如,对于Class组件会调用生命周期办法以及render办法。而对于DOM节点,它执行DOM mutation。因而,每个React元素都被转换为相应类型的Fiber节点。节点形容了须要实现的"work"。
能够将Fiber节点看作一种数据解构,示意一个work单元。,Fiber架构还提供了一种跟踪、调度、暂停和停止work的办法。
React会在首次将React元素转换为Fiber节点时,应用createFiberFromTypeAndProps
函数创立Fiber节点。在更新阶段会复用Fiber节点,并应用React元素上的数据更新Fiber节点上的属性。亦或者挪动,删除Fiber节点。
React源码中的ChildReconciler函数蕴含了Fiber节点中所有的work
React会为每一个React元素创立一个Fiber节点,咱们会失去一个Fiber节点树
Fiber节点树是通过链表的模式存储的,每一个Fiber都领有child(第一个子节点的援用),sibling(第一个兄弟节点的援用)和return(父节点的援用),来示意层级关怀。更多内容请参考这篇文章React Fiber为什么应用链表来设计组件树
current tree 和 progress tree
在第一次渲染实现后,React会生成一个Fiber树。该树映射了应用程序的状态,这颗树被称为current tree
。当应用程序开始更新时,React会构建一个workInProgress tree
, workInProgress tree
映射了将来的状态。
所有的"work"都是在workInProgress tree
上的Fiber节点上进行的。当React开始遍历current tree
时,它会为每一个现有的Fiber节点创立一个备份(alternate字段),alternate节点形成了workInProgress tree
。当所有更新和相干的"work"实现。workInProgress tree
会刷新到屏幕上。workInProgress tree
此时变为了current tree
。
React的外围准则之一是"一致性", 它总是一次性更新DOM, 不会显示局部后果. workInProgress就是一个用户不可见的"草稿", React在它下面解决所有组件, 解决实现后将它再刷新到界面上.
在React的源码中,有很多从workInProgress tree
和current tree
中获取Fiber节点的函数。比方上面这个函数签名
function updateHostComponent(current, workInProgress, renderExpirationTime) { ...}
workInProgress tree
的Fiber节点领有current tree
对应节点的援用。反之亦然。
副作用
咱们能够将React组件视为应用state和props计算UI的函数。其余的流动,比方手动批改DOM,调用生命周期都应该被视作一种副作用。在React的文档中也提及了这一点
你之前可能曾经在 React 组件中执行过数据获取、订阅或者手动批改过 DOM。咱们对立把这些操作称为“副作用”(side-effects),或者简称为“作用”(effects)。因为它们会影响其余组件,并且在渲染期间无奈实现。
大多数state和props的更新都会导致副作用。利用effects是一种work类型。因而Fiber节点是一种跟踪更新和effects的便捷机制,每一个Fiber节点都有与之相关联的effects。它们被编码在effectTag字段之中。
Fiber中的effects定义了解决更新之后须要做的"work"。对于DOM元素,"work"蕴含了增加,更新,删除。对于类组件,包含了更新ref,调用componentDidMount和componentDidUpdate生命周期办法。还有其余effects对应于其余类型的Fibber。
Effects list
React解决更新十分快。为了达到更好的性能程度,采纳了一些乏味的技术。其中之一就是将具备effects的Fiber节点,构建为线性列表,以不便疾速迭代。迭代线性列表要比迭代树快的多,因为不须要迭代没有side-effects的节点。
effects list的目标是是标记出具备DOM更新,或其它与之关联的其余effects的节点。effects list是finishedWork树的子集。在workInProgress tree
和current tree
中应用nextEffect属性链接在一起。
丹·阿布拉莫夫(Dan Abramov)将Effects list提供了一个比喻。将Fiber设想成一颗圣诞树,用圣诞灯将所有无效的节点连贯在一起。
为了可视化这一点,让咱们设想上面的Fiber树,其中高亮显示的节点有一些“work”要做。
例如,咱们的更新导致将c2插入到DOM中,d2和c1更改属性,b2触发生命周期办法。 Effects list列表将把它们链接在一起,这样React就能够遍历时跳过其余节点。
能够看到具备effects的节点如何链接在一起。当遍历节点时,React应用firstEffect指针确定列表的起始地位。上图的Effects list能够用下图示意
Fiber节点树的根
React利用都有一个或者多个充当容器的DOM元素
const domContainer = document.querySelector('#container');ReactDOM.render(React.createElement(ClickCounter), domContainer);
React会为容器创立FiberRoot对象,能够应用容器的DOM援用拜访Fiber root对象:
// Fiber root对象const fiberRoot = query('#container')._reactRootContainer._internalRoot
Fiber root是React保留对Fiber树援用的中央,Fiber树存储在Fiber root对象的current属性中
// Fiber树const hostRootFiberNode = fiberRoot.current
Fiber树的第一个节点是一种非凡的类型节点,叫做HostRoot。它在外部创立,是最顶层组件的父组件。通过HostRoot节点的stateNode属性能够拜访FiberRoot节点.
// Fiber root对象const fiberRoot = query('#container')._reactRootContainer._internalRoot// hostRootconst hostRootFiberNode = fiberRoot.current// truehostRootFiberNode.stateNode === fiberRoot
咱们能够从HostRoot来拜访和摸索整个Fiber树。或者能够通过组件的实例中取得单个Fiber节点
compInstance._reactInternalFiber
Fiber节点构造
ClickCounter组件的Fiber节点构造:
{ stateNode: new ClickCounter, type: ClickCounter, alternate: null, key: null, updateQueue: null, memoizedState: {count: 0}, pendingProps: {}, memoizedProps: {}, tag: 1, effectTag: 0, nextEffect: null}
span DOM元素的Fiber节点构造:
{ stateNode: new HTMLSpanElement, type: "span", alternate: null, key: "2", updateQueue: null, memoizedState: null, pendingProps: {children: 0}, memoizedProps: {children: 0}, tag: 5, effectTag: 0, nextEffect: null}
Fiber节点上有很多字段。咱们之前曾经形容了alternate(备份节点),effectTag(记录与之关联的effects), nextEffect(连贯具备effects的Fiber节点使其成为线性节点)
stateNode属性
保留对class组件实例的援用, DOM节点或其余与Fiber节点相关联的React元素类实例的援用。一般来说, 咱们能够说这个属性被用于保留与以后Fiber相干的本地状态。
type属性
定义与此Fiber节点相关联的函数或者类。对于class组件,type属性指向构造函数。对于DOM元素,type属性指向HTML标记。我常常用这个字段来判断这个Fiber节点与那个元素相干。
tag属性
定义Fiber节点的类型。在协调期间应用它确定须要做的"work"。如之前所述"work"取决于React元素的类型。createFiberFromTypeAndProps函数将React元素映射成绝对应的Fiber节点类型。
在咱们的例子中。ClickCounter的tag为1,示意为ClassComponent。span的tag为5,标记为HostComponent。
updateQueue属性
state更新和回调,DOM更新的队列。
memoizedState属性
用于创立输入Fiber的state。在解决更新的时候,它映射的是以后界面上出现的state。
memoizedProps属性
在上一次渲染过程中用来创立输入的Fiber props。
pendingProps属性
曾经更新后的Fiber props。须要用于子组件和DOM元素。
key属性
一组children中的惟一标示。帮忙React确定那些产生了更改,新增或删除。更具体的解释在这里
总结
残缺的Fiber构造, 能够在这里看到,在下面的阐明省略了很多的字段比方child,sibling并return。这三个字段是形成链表树结构的要害。以及expirationTime、childExpirationTime和mode,这些字段是特定于Scheduler的。
通用算法
React分两个阶段执行work:render(渲染)和 commit(提交)
在render(渲染)阶段,React将更新利用于通过setState或React.render调度的组件, 并找出须要在UI中更新的内容。
如果是初始渲染,React将为render办法返回的每一个元素创立新的Fiber节点。在之后的更新中,将从新应用和更新现有的Fiber节点。
render阶段会构建一个带有side-effects(副作用)的Fiber节点树。effects形容了下一个commit(提交)阶段须要实现的“work”。在commit(提交)阶段,React会应用标记有effects的Fiber节点并将其利用于实例上。遍历Effects list执行DOM更新和其余对用户可见的更改。
请切记,render阶段的工作是能够异步执行的,React依据可用工夫解决一个或者多个Fiber节点。当产生一些更重要的事件时,React会进行并保留已实现的工作。等重要的事件解决实现后,React从中断处持续实现工作。然而有时可能会放弃曾经实现的工作,从顶层从新开始。此阶段执行的工作是对用户是不可见的,因而能够实现暂停。然而在commit(提交)阶段始终是同步的它会产生用户可见的变动, 例如DOM的批改. 这就是React须要一次性实现它们的起因。
调用生命周期函数应用React的“work”之一。在render阶段调用这些生命周期办法:
- UNSAFE_componentWillMount (deprecated)
- UNSAFE_componentWillReceiveProps (deprecated)
- getDerivedStateFromProps
- shouldComponentUpdate
- UNSAFE_componentWillUpdate (deprecated)
- render
因为render阶段不会产生DOM更新之类的副作用,因而React能够异步地对组件进行异步解决更新(甚至可能在多个线程中进行)。然而带有UNSAFE_前缀的生命周期函数经常会被误用,开发者会把副作用增加到这些生命周期函数中。这可能会导致异步渲染呈现问题
在commit阶段调用这些生命周期办法,这些生命周期办法在commit阶段执行,所以它们可能蕴含副作用并波及DOM更新。
- getSnapshotBeforeUpdate
- componentDidMount
- componentDidUpdate
- componentWillUnmount
渲染(render)阶段
协调算法应用renderRoot函数从最顶层的HostRoot节点开始,跳过曾经解决过的节点,直到找到work未实现的节点为止。例如, 当在组件树深处调用setState办法, React 从顶部开始疾速的跳过所有父级节点间接取得调用setState办法的组件。
WorkLoop
所有的Fiber节点在render阶段都会在WorkLoop中被解决。这是循环同步局部的实现:
function workLoop(isYieldy) { if (!isYieldy) { while (nextUnitOfWork !== null) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); } } else {...}}
在下面的代码中,nextUnitOfWork放弃了对workInProgress tree
中一个有工作要解决的Fiber节点的援用。在React遍历Fiber树时,会应用nextUnitOfWork判断是否有未实现"work"的Fiber节点。当节点解决实现“work”后,nextUnitOfWork会指向下一个Fiber节点的援用或者为null。当nextUnitOfWork为null时,React会退出WorkLoop,并筹备进入到commit阶段。
有四个次要的办法用于遍历树,并启动或实现工作:
- performUnitOfWork
- beginWork
- completeUnitOfWork
- completeWork
为了演示如何应用它们。请查看上面遍历Fiber树的演示动画。演示动画中应用了这些函数的简化实现。咱们能够通过演示看到,首先解决子节点的“work”,而后解决父节点的“work”。
应用直线连贯代表同级,应用折线连贯的代笔子级
逐渐拆分下React遍历Fiber树的过程(首先解决子节点的“work”,而后解决父节点的“work”):
- beginWork a1
- beginWork b1,completeWork b1
- beginWork b2
- beginWork c1
- beginWork d1, completeWork d1
- beginWork d2, completeWork d2
- completeWork c1
- completeWork b2
- beginWork b3
- beginWork c2,completeWork c2
- completeWork b3
- completeWork a1
这个是视频的连贯, 从概念上将"begin"看成进入组件,“complete”看成来到组件。
咱们首先看下beginWork和performUnitOfWork这两个函数:
function performUnitOfWork(workInProgress) { let next = beginWork(workInProgress); if (next === null) { next = completeUnitOfWork(workInProgress); } return next;}function beginWork(workInProgress) { console.log('work performed for ' + workInProgress.name); return workInProgress.child;}
performUnitOfWork从workInProgress tree中接管一个Fiber节点。而后调用beginWork开始解决Fiber节点的work。为了演示,这里只是log了Fiber节点的name字段示意work曾经实现。函数beginWork总是返回指向循环中下一个子节点或null。
如果有下一个子节点, 它将在workLoop函数中调配给nextUnitOfWork。如果没有子节点,React就晓得了达到了分支的结尾。就会实现以后Fiber节点的work。React会执行它兄弟节点的工作,最初回溯到父节点。这是在completeUnitOfWork中实现的。
function completeUnitOfWork(workInProgress) { while (true) { let returnFiber = workInProgress.return; let siblingFiber = workInProgress.sibling; nextUnitOfWork = completeWork(workInProgress); if (siblingFiber !== null) { // 如果有同级,则返回它。以继续执行同级的工作 return siblingFiber; } else if (returnFiber !== null) { // 回溯到上一级 workInProgress = returnFiber; continue; } else { // 曾经到了root节点 return null; } }}function completeWork(workInProgress) { console.log('work completed for ' + workInProgress.name); return null;}
当workInProgress节点没有子节点时,会进入此函数。在实现以后Fiber的工作后,会查看是否有兄弟节点。如果有,返回同级的兄弟节点的指针,调配给nextUnitOfWork。React将会从兄弟节点开始工作。只有解决完子节点所有分支之后, 才会回溯到父节点(所有子节点解决实现后,才会回溯到父节点)。
从实现能够看出,completeUnitOfWork次要用于迭代,次要工作都是beginWork和completeWork函数中进行的。
残缺的示例
这里是残缺的示例(beginWork,performUnitOfWork,completeUnitOfWork,completeWork的繁难实现)
// 首先构建链表树const a1 = {name: 'a1', child: null, sibling: null, return: null};const b1 = {name: 'b1', child: null, sibling: null, return: null};const b2 = {name: 'b2', child: null, sibling: null, return: null};const b3 = {name: 'b3', child: null, sibling: null, return: null};const c1 = {name: 'c1', child: null, sibling: null, return: null};const c2 = {name: 'c2', child: null, sibling: null, return: null};const d1 = {name: 'd1', child: null, sibling: null, return: null};const d2 = {name: 'd2', child: null, sibling: null, return: null};a1.child = b1;b1.sibling = b2;b2.sibling = b3;b2.child = c1;b3.child = c2;c1.child = d1;d1.sibling = d2;b1.return = b2.return = b3.return = a1;c1.return = b2;d1.return = d2.return = c1;c2.return = b3;// 以后的指针是a1let nextUnitOfWork = a1;workLoop();// 开始工作循环function workLoop() { while (nextUnitOfWork !== null) { nextUnitOfWork = performUnitOfWork(nextUnitOfWork); }}function performUnitOfWork(workInProgress) { let next = beginWork(workInProgress); if (next === null) { next = completeUnitOfWork(workInProgress); } return next;}function beginWork(workInProgress) { log('work performed for ' + workInProgress.name); return workInProgress.child;}function completeUnitOfWork(workInProgress) { while (true) { let returnFiber = workInProgress.return; let siblingFiber = workInProgress.sibling; nextUnitOfWork = completeWork(workInProgress); if (siblingFiber !== null) { return siblingFiber; } else if (returnFiber !== null) { workInProgress = returnFiber; continue; } else { return null; } }}function completeWork(workInProgress) { log('work completed for ' + workInProgress.name); return null;}function log(message) { let node = document.createElement('div'); node.textContent = message; document.body.appendChild(node);}
提交(commit)阶段
提交阶段从completeRoot开始。这是React更新DOM,调用getSnapshotBeforeUpdate,componentDidMount,componentDidUpdate,componentWillUnmount等生命周期的中央。
React进入这一阶段时,有两颗树(workInProgress tree和current tree)以及effects list。current tree
示意了以后屏幕上出现的状态。render阶段遍历current tree
时会生成另一颗树,在源码中被称为finishWork或workInProgress,示意将来须要在屏幕上出现的状态。workInProgress tree
和current tree
构造相似。
调试时,如何获取current tree
以及workInProgress tree
?
// current tree// 从容器对象上获取FiberRoot对象const fiberRoot = query('#container')._reactRootContainer._internalRoot// 获取current treeconst currentTree = fiberRoot.current// 获取workInProgress treeconst workInProgressTree = fiberRoot.current.alternate
提交(commit)阶段,次要执行commitRoot函数,执行以下的操作:
- 在有Snapshot标记的Fiber节点上调用getSnapshotBeforeUpdate生命周期办法。
- 在有Deletion标记的Fiber节点上调用componentWillUnmount生命周期办法。
- 执行所有的 DOM 插入, 更新, 删除。
- 将
workInProgress tree
设置为current tree
。 - 在有Placement标记的Fiber节点上调用componentDidMount生命周期办法。
- 在有Update标记的Fiber节点上调用componentDidUpdate生命周期办法。
在调用getSnapshotBeforeUpdate办法后,React将commit,Fiber树中所有的副作用。分为两步:
第一步,执行所有的DOM插入,更新,删除和ref卸载。而后将workInProgress tree
设置为current tree
树。这是在第一步实现之后,第二步之前实现的。因而在componentWillUnmount生命周期办法在执行期间,状态仍然是更新之前的。而componentDidMount/componentDidUpdate执行时的状态是更新之后的。第二步,执行其余生命周期办法和ref回调,这些办法作为独自的过程被执行。
commitRoot办法的预览:
function commitRoot(root, finishedWork) { // 用来执行getSnapshotBeforeUpdate commitBeforeMutationLifecycles() // 用户更新DOM,以及执行componentWillUnmount commitAllHostEffects(); root.current = finishedWork; // 调用componentDidUpdate和componentDidMount生命周期的中央 commitAllLifeCycles();}
这些子函数,外部都蕴含了一个循环。循环遍历effects list,并查看effects的类型。当发现类型和子函数的目标雷同时,就利用它。
Pre-mutation lifecycle methods
遍历effects list,并查看节点是否具备Snapshot effect的源代码:
function commitBeforeMutationLifecycles() { while (nextEffect !== null) { const effectTag = nextEffect.effectTag; if (effectTag & Snapshot) { const current = nextEffect.alternate; commitBeforeMutationLifeCycles(current, nextEffect); } nextEffect = nextEffect.nextEffect; }}
如果是class组件,调用getSnapshotBeforeUpdate生命周期办法。
DOM updates
commitAllHostEffects是React执行DOM更新的中央。React会把componentWillUnmount作为commitDeletion删除过程中的一部分。
function commitAllHostEffects() { switch (primaryEffectTag) { case Placement: { commitPlacement(nextEffect); ... } case PlacementAndUpdate: { commitPlacement(nextEffect); commitWork(current, nextEffect); ... } case Update: { commitWork(current, nextEffect); ... } case Deletion: { commitDeletion(nextEffect); ... } }}
Post-mutation lifecycle methods
commitAllLifecycles是React调用所有残余生命周期办法componentDidUpdate和componentDidMount的中央。
总结
React源码很简单,Max Koretskyi的这篇文章内容也很多,所有总结下这篇博客的要点:
- React的外围是跟踪组件状态变动并将更新后的状态更新到屏幕上。在React中,咱们把这个过程称为 reconciliation (协调)。
- 协调期间的各种流动在Fiber架构中统称为work(工作),work的类型取决于React元素的类型,React元素的类型取决于React.createElement函数的第一个参数类型。
- 协调期间React元素会被合并到Fiber节点树之中,每一种React元素都会一种对应的Fiber节点。Fiber节点不会反复创立,会在第一次渲染后重用。
- React会为每一个React元素创立一个Fiber节点,咱们会失去一个Fiber节点树。Fiber节点树是通过链表的模式存储的。每一个Fiber节点都领有child,sibling和return字段,用于中断复原遍历。
- 在首次渲染时会生成一棵Fiber树。被称为
current tree
。当开始更新时,React会构建一个workInProgress tree
。 current tree
代表了以后的状态,workInProgress tree
代表了将来的状态。- 在commit阶段
workInProgress tree
会被设置为current tree
。 - React在遍历
current tree
时,会为每一个Fiber节点创立一个alternate字段,alternate字段保留了Fiber节点的备份,alternate字段上保留的备份Fiber节点形成了workInProgress tree
。 - 数据获取、订阅或者手动批改过DOM都统称为副作用(side-effects)或者简称为“作用”(effects)。因为它们会影响其余组件,并且在渲染期间无奈实现。
- effects是一种work类型。因而Fiber节点是一种跟踪更新和effects的便捷机制,每一个Fiber节点都有与之相关联的effects。它们被编码在effectTag字段之中。Fiber中的effects定义了解决更新之后须要做的"work"。对于DOM元素,"work"蕴含了增加,更新,删除。对于类组件,包含了更新ref,调用componentDidMount和componentDidUpdate生命周期办法。还有其余effects对应于其余类型的Fiber。
- React会将具备effects的Fiber节点,构建为线性列表,以不便疾速迭代。firstEffect是列表的开始地位,应用nextEffect属性将节点链接在一起(构建成线性列表)。
- 能够通过容器DOM元素,获取FiberRoot对象
query('#container')._reactRootContainer._internalRoot
。 - FiberRoot对象的current属性,是current tree的第一个节点,被称为hostRoot。
fiberRoot.current
。 - hostRoot节点的stateNode属性,指向FiberRoot对象。
- 获取
workInProgress tree
,fiberRoot.current.alternate
。 - Fiber节点构造见上文。
- React分两个阶段执行work:render(渲染)和 commit(提交)
- render阶段的工作是能够异步执行的,React依据可用工夫解决一个或者多个Fiber节点。当产生一些更重要的事件时,React会进行并保留已实现的工作。等重要的事件解决实现后,React从中断处持续实现工作。然而有时可能会放弃曾经实现的工作,从顶层从新开始。此阶段执行的工作是对用户是不可见的,因而能够实现暂停。
- commit(提交)阶段始终是同步的它会产生用户可见的变动, 例如DOM的批改. 这就是 React须要一次性实现它们的起因。
- 所有的Fiber节点在render阶段都会在WorkLoop中被解决。WorkLoop中次要有四个函数,performUnitOfWork,beginWork,completeUnitOfWork,completeWork。WorkLoop首先解决子节点的“work”,所有子节点解决实现后,回溯而后解决父节点的“work”。从概念上将"begin"看成进入组件,“complete”看成来到组件。
- 提交(commit)阶段分为两步实现。第一步,执行所有的DOM插入,更新,删除和ref卸载以及执行componentWillUnmount生命周期办法。第二步,执行其余生命周期办法和ref回调,这些办法作为独自的过程被执行。第一步和第二步两头会将
workInProgress tree
设置为current tree
树。
参考
- Inside Fiber: in-depth overview of the new reconciliation algorithm in React