当前位置: 首页 > Web前端 > HTML5

React系列——ReactFiber架构介绍资料汇总(翻译+中文资料)

时间:2023-04-05 15:52:38 HTML5

原文react-fiber-architecture介绍ReactFiber是对React核心算法的持续再实现。它是React团队两年多的研究成果。ReactFiber的目标是提高其在动画、布局和手势等领域的适用性。它的主要特点是增量渲染:将渲染工作分成块并将其分布在多个帧上的能力。其他主要功能包括在更新过程中暂停、中止或重用工作的能力;为不同类型的更新分配优先级的能力;和新的并发原语。关于本文档Fiber介绍了几个仅通过查看代码难以完成的新颖概念。本文档开始是我们在React项目中与Fiber实现一起使用的一系列笔记。随着它的发展,我意识到它对其他人也可能是有用的资源。我尽量使用最通俗易懂的语言,并通过明确定义关键术语来避免行话。在可能的情况下,我还大量链接到外部资源。请注意,我不在React团队中,也不代表任何权威。这不是官方文件。我已经请React团队的一名成员检查它的准确性。这也是一项正在进行的工作。Fiber是一个正在进行的项目,在完成之前可能会进行重大重构。我也试图在这里记录它的设计。非常欢迎改进和建议。我的目标是在阅读本文档后,您将了解Fiber的实现,甚至最终能够回馈给React。先决条件我强烈建议您在继续之前熟悉以下资源:React组件、元素和实例-“组件”通常是一个重载的术语。牢牢掌握这些术语至关重要。Reconciliation-React协调算法的高级描述。ReactBasicTheoryConcepts-React概念模型的描述。其中一些在初读时可能没有意义。没关系,随着时间的推移它会变得更有意义。ReactDesignPrinciples-特别注意调度部分。它很好地解释了ReactFiber的工作原理。如果您还没有查看先决条件,请先阅读。在我们深入研究新内容之前,让我们回顾一些概念。什么是reconciliationreconciliationReact算法,用来比较2棵树,判断哪些部分需要改变。更新用于呈现React应用程序的数据更改。通常是setState的结果。最终导致重新渲染。React的API的核心思想是更新导致整个应用程序重新渲染。这允许开发人员以声明方式进行推理,而不必担心如何有效地将应用程序从任何特定状态转换到另一个状态(A到B、B到C、C到A等)。事实上,每次更改都重新渲染整个应用程序只适用于最琐碎的应用程序;在实际应用中,性能方面的成本非常高。React进行了优化,可以创建整个应用程序需要重新渲染的外观,同时保持良好的性能。大多数这些优化都是协调的一部分。协调是一种通常被理解为“虚拟DOM”的算法。高级描述如下所示:当您呈现React应用程序时,会生成描述该应用程序的节点树并将其保存在内存中。然后将该树刷新到渲染环境-例如,在浏览器中,将其转换为一组DOM操作。当应用程序更新时(通常通过setState),会生成一棵新树。新树与之前的树不同,是为了计算需要更新表示的应用程序的操作。尽管Fiber完全重写了协调器,但React文档中描述的高级算法在很大程度上是相同的。重点是:假设不同的组件类型产生完全不同的树。React并没有试图区分它们,而是完全取代了旧树。使用键执行列表区分。密钥应该是“稳定的、可预测的、唯一的”。协调和渲染DOM只是React可以渲染的渲染环境之一,原生的iOS和Android视图也可以通过ReactNative进行。(这就是为什么“虚拟DOM”有点用词不当)。它可以支持如此多的目标,因为React的设计使得协调和呈现是不同的阶段。协调器负责计算树的哪些部分发生了变化;渲染器然后使用该信息来实际更新渲染的应用程序。这种分离意味着ReactDOM和ReactNative可以使用它们自己的渲染器,同时共享React核心提供的相同协调器。Fiber重新实现了协调器。虽然渲染器需要更改以支持(并利用)新架构,但它主要不关心渲染。调度调度是确定何时应该完成工作的过程。处理任何必须执行的计算。工作通常是更新的结果(例如setState)。React的设计原则文档在这个主题上非常好,我将在这里引用它:在当前的实现中,React递归地遍历树并调用渲染函数,一次更新整个树。但是,将来可能会延迟某些更新以避免丢帧。这是React设计中的一个常见主题。一些流行的库实现了一种“推送”方法,在新数据可用时执行计算。然而,React坚持“拉”的方法,计算可以延迟到必要的时候。React不是一个通用的数据处理库。这是一个用于构建用户界面的库。我们认为它的独特定位是让应用程序知道哪些计算是相关的,哪些不是。如果某些东西在屏幕外,我们可以推迟与之相关的任何逻辑。如果数据到达的速度快于帧速率,我们可以合并批量更新。我们可以优先考虑来自用户交互的工作(例如由单击按钮引起的动画)而不是重要的后台工作(例如刚刚从网络加载的新内容)以避免丢失帧。重点是:在UI中,没有必要立即应用每个更新。事实上,这样做可能会造成浪费,导致丢帧并降低用户体验。不同类型的更新有不同的优先级——动画更新需要比数据存储更新更快地完成。基于推送的方法要求应用程序(您,程序员)决定如何安排工作。基于拉取的方法允许框架(React)更智能并为您做出决策。目前,React并没有在很大程度上利用调度;更新整个子树的结果会立即重新呈现。改造React的核心算法以利用调度是Fiber背后的驱动思想。现在我们准备好进入Fiber实施。下一节比我们目前讨论的内容更具技术性。在继续之前,请确保您对前面的内容感到满意。什么是纤维?我们即将讨论ReactFiber架构的核心。Fiber比应用程序开发人员通常认为的要低得多。如果您对自己的理解感到沮丧,请不要气馁。继续尝试,它最终会有意义。(当你最终得到它时,请建议如何改进这部分。)我们开始吧!我们已经确定Fiber的主要目标是使React能够利用调度。具体来说,我们需要能够暂停工作并稍后返回。为不同类型的工作分配优先级。重复之前完成的工作。如果不再需要,请中止工作。为此,我们首先需要一种将工作分解为多个单元的方法。从某种意义上说,这是纤维。纤程代表一个工作单元。更进一步,让我们回到React组件作为数据函数的概念,通常表示为v=f(d)因此渲染React应用程序类似于调用一个函数,该函数的主体包含对其他函数的调用等。在考虑纤维时,这个比喻很有用。计算机通常跟踪程序执行的方式是使用调用堆栈。当一个函数被执行时,一个新的栈帧被添加到栈中。此堆栈帧表示函数执行的工作。处理UI时的问题是,如果一次执行太多工作,可能会导致动画掉帧并看起来不稳定。更重要的是,如果某些工作被较新的更新所取代,则可能是不必要的。这就是UI组件和功能之间的比较失败的地方,因为组件比一般的功能有更多的具体问题。较新的浏览器(和ReactNative)实现了API来帮助解决这个确切的问题:requestIdleCallback安排一个低优先级函数在空闲期间调用,requestAnimationFrame安排一个高优先级函数在下一个动画帧上调用。问题在于,为了使用这些API,您需要一种方法将渲染工作分解为增量单元。如果你只是依赖调用栈,它会一直工作到栈为空。如果我们可以自定义调用堆栈的行为以优化UI渲染,那不是很好吗?如果我们可以随意中断调用堆栈并手动操作堆栈帧,那不是很好吗?这就是ReactFiber的用途。Fiber重新实现堆栈,特别是针对React组件。您可以将单根光纤视为虚拟堆栈框架。重新实现堆栈的好处是您可以将堆栈帧保存在内存中,然后执行它们(无论何时)。这对于实现我们安排的目标至关重要。除了调度之外,堆栈帧的手动处理释放了并发和错误边界等功能的潜力。我们将在以后的章节中介绍这些主题。在下一节中,我们将更多地关注纤维结构。Fiber的结构说明:随着我们对实现细节的了解越来越具体,事情发生变化的可能性也会增加。如果您发现任何错误或过时的信息,请提交PR。具体来说,纤程是一个JavaScript对象,其中包含有关组件及其输入和输出的信息。一个纤程对应一个栈帧,也对应一个组件的实例。这些都是属于光纤的一些重要领域。(此列表并不详尽。)类型和键Fiber的类型和键与React元素的工作方式相同。(实际上,这两个字段是在从元素创建纤程时直接复制的。)纤程的类型描述了其对应的组件。对于复合组件,类型是函数或类组件本身。对于宿主组件(div、span等),类型是字符串。从概念上讲,类型是一个函数(如v=f(d)),其执行由堆栈帧跟踪。根据类型,在协调期间使用密钥来确定光纤是否可以重复使用。child和sibling字段指向其他fiber并描述fiber的递归树结构。子纤维对应于组件的render方法返回的值。所以在下面的例子中functionParent(){return}Parent的childfiber对应Child。sibling字段说明了渲染返回多个孩子的情况(Fiber的新特性):functionParent(){return[,]}第一个孩子。所以在这个例子中,Parent的孩子是Child1,而Child1的兄弟姐妹是Child2。回到我们的函数类比,您可以将子光纤视为尾调用函数。returnreturnfiber是程序处理完当前纤程后返回的纤程。它在概念上与堆栈帧的返回地址相同。它也可以被认为是母体纤维。如果纤程有多个子纤程,则每个子纤程的返回纤程都是父纤程。所以在前面的例子中,Child1和Child2的返回fiber是Parent。pendingProps和memoizedProps从概念上讲,props是函数的参数。纤程的pendingProps在执行开始时设置,memoizedProps在执行结束时设置。当传入的pendingProps等于memoizedProps时,说明可以复用fiber之前的输出,避免不必要的工作。pendingWorkPriority一个数字,指示纤程所代表的工作的优先级。ReactPriorityLevel模块列出了不同的优先级及其代表的内容。除了为0的NoWork外,数字越大表示优先级越低。例如,您可以使用以下函数来检查纤程的优先级是否至少与给定级别一样高:functionmatchesPriority(fiber,priority){returnfiber.pendingWorkPriority!==0&&fiber.pendingWorkPriority<=priority}这功能仅使用只是为了澄清;它实际上并不是ReactFiber代码库的一部分。调度程序使用优先级字段来搜索要执行的下一个工作单元。该算法将在后面的章节中讨论。flushflushfiber的替代方法是将其输出渲染到屏幕上。正在进行的工作尚未完成的纤程;从概念上讲,一个尚未返回的堆栈帧。在任何时候,一个组件实例最多有两个纤程与之对应:当前的、已刷新的纤程和进行中的纤程。当前纤程的交替是正在进行的工作,进行中的交替是当前纤程。可以使用名为cloneFiber的函数创建光纤替换。cloneFiber并不总是创建一个新对象,而是尝试重用纤程的备用(如果存在),从而最大限度地减少分配。您应该将alternate字段视为一个实现细节,但它在代码库中很常见,因此在这里讨论它很有用。输出宿主组件React应用程序的叶节点。它们特定于呈现环境(例如,在浏览器应用程序中它们是“div”、“span”等)。在JSX中,它们使用小写的标签名称表示。从概念上讲,纤程的输出是函数的返回值。每个纤程最终都有一个输出,但输出仅由叶节点处的宿主组件创建。然后将输出通过管道传送到树上。输出是最终呈现给渲染器的内容,以便它可以随着渲染环境的变化而刷新。渲染器负责定义如何创建和更新输出。未来的部分就这些了,但文档远未完成。后面的部分描述了整个生命周期中使用的算法。涵盖的主题包括:调度程序如何找到要执行的下一个工作单元。如何通过纤程树跟踪和传播优先级。调度程序如何知道何时暂停和恢复工作。如何刷新作业并将其标记为完成。副作用(如生命周期方法)如何工作。什么是协程,如何使用协程实现context、layout等功能。相关视频What'sNextforReact(ReactNext2016)相关信息1.全面了解ReactFiber2、React16Fiber源码概览3.什么是ReactFiber4.如何理解ReactFiber架构?5.Under-the-hood-ReactJStree相关论文ASurveyonTreeEditDistanceandRelatedProblems