react-fiber-architecturejavascript
React Fiber的目标是提升其对动画,布局和手势等领域的适用性。它的主体特征是增量渲染:可以将渲染工做分割成块,并将其分散到多个帧中。html
其余主要功能包括在进行更新时暂停,停止或从新使用工做的能力;为不一样类型的更新分配优先权的能力;和新的并发原语。java
Fiber引入了几个新颖的概念,很难经过查看代码来完成。这个文档是咱们在React项目中随着Fibre实现的一系列笔记开始的。随着它的发展,我意识到它也可能成为其余人的有用资源。react
我会尝试尽量使用最普通的语言,并经过明肯定义关键术语来避免行话。在可能的状况下,我也会大量链接外部资源。 git
请注意,我不在React团队,也不会从任何权威机构发言。这不是一个正式的文件。我已经要求React团队的成员对其进行检查以确保准确性。 程序员
这也是一个正在进行的工做。Fiber是一个正在进行的项目,在完成以前可能会经历重大的重构。我也试图在这里记录它的设计。改进和建议是很是受欢迎的。 github
个人目标是在阅读本文档以后,您将会理解Fiber的实施状况,并最终甚至可以回馈给React。算法
我强烈建议您在继续以前熟悉如下资源: c#
React Components, Elements, and Instances - “ Component”一般是一个重载的术语。紧紧掌握这些术语相当重要。 浏览器
Reconciliation - 对React reconciliation算法的高级描述。
React基本理论概念 - 对React概念模型的描述。其中一些内容在第一次阅读时可能没有意义。不要紧,随着时间的推移会更有意义。
React设计原则 - 特别注意scheduling部分。它很好的解释了React Fiber的工做原理。
若是你尚未看先决条件,请先看完它。
在咱们深刻研究新的东西以前,让咱们回顾一下几个概念。
reconciliation
React算法,用来比较2颗树,以肯定哪些部分须要改变。
更新
用于呈现React应用的数据更改。一般是`setState`的结果。最终致使从新渲染。
React的API的核心思想认为更新会致使整个应用程序从新渲染。这容许开发人员以声明的方式进行推理,而不用担忧如何有效地将应用程序从任何特定状态转换到另外一个状态(从A到B,从B到C,从C到A等等)。
实际上,在每次更改时从新渲染整个应用程序只适用于最琐碎的应用程序;在实际的应用程序中,性能方面的代价很是高昂。 React具备优化功能,可在保持良好性能的同时建立整个应用程序须要从新呈现的外观。这些优化的大部份内容是reconciliation的一部分。
Reconciliation是被广泛理解为“虚拟DOM”的算法。高级描述以下所示:当您渲染React应用程序时,会生成一个描述应用程序的节点树并保存在内存中。而后将该树刷新到渲染环境 - 例如,在浏览器中,将其转换为一组DOM操做。当应用程序更新(一般经过setState),生成一个新的树。新树与前一棵树有区别,以计算须要更新呈现的应用程序的操做。
尽管Fibre是对reconciler的彻头彻尾的重写,但React文档中描述的高级算法在很大程度上是相同的。关键是:
DOM只是React能够渲染的渲染环境之一,还能够经过React Native进行本地iOS和Android视图。 (这就是为何“虚拟DOM”有点用词不当)。
它能够支持如此多目标是由于React的设计使reconciliation和渲染是分开的阶段。reconciler完成了计算树的哪些部分已经改变的工做;渲染器而后使用该信息实际更新呈现的应用程序。
这种分离意味着React DOM和React Native可使用他们本身的渲染器,同时共享由React核心提供的相同的reconciler。
Fiber从新实现了reconciler。虽然渲染者须要改变以支持(并利用)新的架构,但它并不主要关心渲染。
scheduling
肯定什么时候应该进行工做的过程。
工做
任何须须执行的计算。工做一般是更新的结果(例如setState)。
React的设计原则文档在这个主题上很是好,我只是在这里引用它:
在当前实现中,React递归地遍历树,并在单个tick中调用整个更新树的render函数。可是,未来可能会延迟一些更新以免丢帧。这是React设计中的一个常见主题。当新数据可用时,一些流行的库实现了“push”方法,其中计算被执行。然而,React坚持“pull”的方法,计算能够延迟到必要的时候。
React不是一个通用的数据处理库。这是一个创建用户界面的库。咱们认为它是惟必定位在一个应用程序来知道如今哪些计算是相关的,哪些不是。
若是有什么东西在屏幕外,咱们能够推迟任何与之相关的逻辑。若是数据比帧速率更快到达,咱们能够合并批量更新。咱们能够优先考虑来自用户交互的工做(好比点击按钮形成的动画)而不是重要的后台工做(好比刚刚从网络加载的新内容),以免丢失帧。
关键是:
目前,React并无以重要的方式利用scheduling;整个子树的更新结果当即被从新渲染。检修React的核心算法以利用scheduling是Fiber背后的驱动理念。
如今咱们已经准备好进入Fiber的实施。接下来的部分比咱们迄今为止所讨论的更具技术性。在继续以前,请确保您对前面的内容感到满意。
咱们即将讨论React Fiber架构的核心。Fiber比应用程序开发人员一般所想的要底层抽象得多。若是你对本身的理解感到沮丧,不要感到气馁。继续尝试,最终会有意义的。 (当你最终获得它,请建议如何改善这一部分。)
开始了!
咱们已经肯定,Fiber的主要目标是使React可以利用scheduling。具体来讲,咱们须要可以
为了作到这一点,咱们首先须要一种把工做分解成单元的方法。从某种意义上说,这就是fiber。fiber表明一个工做单元。
为了更进一步,让咱们回到React组件做为数据函数的概念,一般表达为
v = f(d)
所以渲染一个React应用程序相似于调用其主体包含对其余函数的调用的函数,等等。这个比喻在思考fiber时颇有用。
计算机一般跟踪程序执行的方式是使用调用堆栈。当一个函数被执行时,一个新的堆栈框架被添加到堆栈中。该堆栈框表示由该函数执行的工做。
在处理UI时,问题是若是一次执行了太多的工做,它可能会致使动画丢帧,看起来不稳定。更重要的是,若是某些工做被更新的更新所取代,那么这些工做多是没必要要的。这是UI组件和函数之间的比较失败的地方,由于组件比通常的函数具备更多特定的问题。
较新的浏览器(和React Native)实现了API来帮助解决这个确切的问题:requestIdleCallback schedules在空闲期间被调用的低优先级函数,requestAnimationFrame schedules在下一个动画帧上被调用的高优先级函数。问题是,为了使用这些API,您须要一种方法来将渲染工做分解为增量单元。若是只依赖调用堆栈,它将继续工做,直到堆栈为空。
若是咱们能够自定义调用堆栈的行为来优化呈现UI,这不是很好吗?若是咱们能够随意中断调用堆栈并手动操做堆栈帧,这不是很好吗?
这就是React Fiber的目的。Fiber从新实现堆栈,专门用于React组件。您能够将单个fiber视为虚拟堆栈帧。
从新实现堆栈的好处是你能够将堆栈帧保存在内存中,而后执行它们(不管什么时候)。这对于完成咱们安排的目标相当重要。
除了调度scheduling,还有手动处理堆栈帧解锁了诸如并发和错误边界之类的功能的潜力。咱们将在之后的章节中介绍这些话题。
在下一节中,咱们将更多地关注fiber的结构。
注意:随着咱们对实现细节的更具体的了解,事情可能发生变化的可能性会增长。若是您发现任何错误或过期的信息,请提交PR。
具体而言,fiber是一个JavaScript对象,包含有关组件,其输入和输出的信息。
fiber对应于堆栈帧,但也对应于组件的一个实例。
这是一些属于fiber的重要领域。 (这个清单并不详尽。)
fiber的type和key与React元素的做用相同。 (实际上,当从一个元素建立一个fiber时,这两个字段直接被复制过来。)
fiber的type描述了它对应的组件。对于复合组件,类型是函数或类组件自己。对于host组件(div,span等),类型是一个字符串。
从概念上讲,type是函数(如在v = f(d)中),其执行被栈帧跟踪。
随着type的不一样,在reconciliation期间使用key来肯定fiber是否能够从新使用。
这些字段指向其余fiber,描述fiber的递归树状结构。
子fiber对应于组件渲染方法返回的值。因此在下面的例子中
function Parent() { return <Child /> }
Parent的child fiber对应于Child。
兄弟领域说明了渲染返回多个children的状况(Fiber中的一个新特性):
function Parent() { return [<Child1 />, <Child2 />] }
child的fiber造成一个单一的链表,head是第一个child。因此在这个例子中,Parent的child是Child1,而Child1的兄弟是Child2。
回到咱们的功能比喻,你能够把一个子fiber想象成一个尾调用函数。
return fiber是程序在处理完当前fiber后返回的fiber。它在概念上与堆栈帧的返回地址相同。它也能够被认为是parent fiber。
若是fiber具备多个子fiber,则每一个子fiber的return fiber是parent。因此在前面的例子中,Child1和Child2的return fiber是Parent。
从概念上讲,props是一个函数的arguments。一个fiber的pendingProps在执行开始时被设置,memoizedProps被设置在最后。
当传入的pendingProps等于memoizedProps时,它代表fiber的先前输出能够被重复使用,避免没必要要的工做。
一个数字,表示fiber所表明的工做的优先级。 ReactPriorityLevel模块列出了不一样的优先级以及它们表明的内容。
除NoWork为0外,较大的数字表示较低的优先级。例如,您可使用如下函数来检查fiber的优先级是否至少与给定级别同样高:
function matchesPriority(fiber, priority) { return fiber.pendingWorkPriority !== 0 && fiber.pendingWorkPriority <= priority }
此函数仅用于说明;它实际上并非React Fiber代码库的一部分。
scheduler使用优先级字段来搜索要执行的下一个工做单元。这个算法将在之后的章节中讨论。
flush
flush fiber是将其输出呈如今屏幕上。
work-in-progress
还没有完成的fiber;从概念上说,一个还没有返回的堆栈帧。
在任什么时候候,一个组件实例最多只有两条fiber对应:当前的,flushed fiber和work-in-progress fiber。
当前fiber的交替是work-in-progress,work-in-progress的交替是当前的fiber。
使用名为cloneFiber的函数,能够建立一个fiber的替代品。 cloneFiber不会老是建立一个新的对象,而是尝试重用fiber的备用,若是它存在,最小化分配。
您应该将备用字段视为实现细节,可是它在代码库中常常出现,所以在此讨论它很是有用。
host component
React应用程序的叶子节点。它们特定于渲染环境(例如,在浏览器应用程序中,它们是“div”,“span”等)。在JSX中,它们使用小写的标记名称来表示。
从概念上讲,fiber的输出是一个函数的返回值。
每一个fiber最终都有输出,但输出仅由host组件在叶子节点建立。而后将输出传送到树上。
输出是最终呈现给渲染器,以便它能够刷新渲染环境的变化。渲染者有责任定义输出是如何建立和更新的。
如今就是这样,可是这个文件还远远没有完成。之后的部分将介绍在整个生命周期中使用的算法。要涵盖的主题包括:
What's Next for React (ReactNext 2016)