反应光纤架构简介

哈Ha! 我提请您注意安德鲁·克拉克Andrew Clark)撰写的文章“反应纤维体系结构”的翻译。


参赛作品


React Fiber是关键React算法的逐步实现。 这是React开发团队为期两年的研究的高潮。


Fiber的目标是在开发任务(例如动画,组织页面上的元素和移动元素)时提高生产率。 它的主要功能是增量渲染:将渲染工作分为多个单元并在多个帧之间分配的能力。


其他关键功能包括暂停,取消或重用DOM树的传入更新的能力,确定不同类型更新的优先级的能力以及原语的协调。


在阅读本文之前,我们建议您熟悉React的基本原理:



复习


什么是和解?


协调是一种React算法,用于区分一个元素树与另一个元素树,以确定需要更换的零件。


更新是用于渲染React应用程序的数据中的更改。 这通常是调用setState方法的结果。 渲染组件的最终结果。


React API的关键思想是考虑更新,好像它们可以导致应用程序的完整呈现。 这使开发人员可以进行声明式操作,而不必担心应用程序从一种状态到另一种状态(从A到B,从B到C,从C到A等)的转换有多合理。


通常,为每个更改呈现整个应用程序仅在最传统的应用程序中有效。 在现实世界中,这会对性能产生不利影响。 该法案包括一些优化措施,这些措施可以创建完整的渲染视图,而不会影响性能的很大一部分。 这些优化大多数都涉及一个称为对帐的过程。


对帐是一种我们习惯称为“虚拟DOM”的算法。 定义听起来像这样:渲染React应用程序时,描述该应用程序的元素树在保留的内存中生成。 然后将此树包含在呈现环境中-使用浏览器应用程序的示例,将其转换为一组DOM操作。 当应用程序状态被更新时(通常通过调用setState),将生成一棵新树。 将新树与前一棵树进行比较,以计算并完全启用重新绘制更新的应用程序所需的那些操作。


尽管Fiber是协调器的紧密实现,但React文档中解释的高级算法几乎相同。


关键概念:


  • 不同类型的组件建议生成实质上不同的树。 React不会尝试比较它们,而只是完全替换旧树。
  • 使用键区分列表。 密钥应“持久,可预测且唯一”。

对帐与渲染


DOM树是React可以绘制的环境之一,其余的可以使用React Native归因于本机iOS和Android视图(这就是Virtual Dom有点不合适的名称的原因)。


React支持这么多目标的原因是因为React是构建为使对帐和呈现是独立的阶段。 协调器正在工作,计算出树的哪些部分已更改,渲染器随后使用此信息来更新先前渲染的树。


这种分离意味着,当使用位于React Core中的相同缓存工具时,React DOM和React Native可以使用它们自己的呈现机制。


光纤是对帐算法的重新设计实现。 它与渲染有间接关系,同时可以更改渲染机制(渲染)以支持新体系结构的所有优点。


规划是确定何时应完成工作的过程。


工作 -必须执行的任何计算。 工作通常是更新的结果(例如,调用setState)。


React体系结构的原理是如此出色,以至于只能用以下引用来描述它们:


在当前的React实现中,它以递归方式遍历树,并在一个滴答声(16 ms)中调用整个更新树上的呈现函数。 但是,将来,他将能够取消一些更新以防止跳帧。
这是关于React Design的经常讨论的主题。 一些流行的库实现了一种“推送”方法,即在有新数据可用时执行计算。 但是,React遵循拉方法,在这种情况下可以在必要时取消计算。
React不是用于处理通用数据的库。 这是用于构建用户界面的库。 我们认为它应该在应用程序中具有唯一的位置,以便确定哪些计算适合当前,哪些不适合。
如果幕后有什么事情,那么我们可以撤消与其相关的所有逻辑。 如果数据到达的速度快于帧渲染速度,则可以合并更新。 我们可以提高用户交互(例如,按下按钮时动画的外观)所导致的工作优先级,而不是在后台进行次重要的工作(呈现从服务器加载的新内容),以防止帧下载。

关键概念:


  • 在用户界面中,立即应用每个更新并不重要; 实际上,这种行为是多余的,它将导致帧下降和UX恶化。
  • 不同类型的更新具有不同的优先级-动画更新应比更新数据存储更快地结束。
  • 基于推送的方法要求应用程序(您,开发人员)决定如何计划工作。 基于拉的方法使框架可以为您做出决策。

目前,React在很大程度上没有计划的优势; 整个子树的更新结果将立即绘制。 仔细选择React内核算法中的元素以应用调度是Fiber的关键思想。


什么是纤维?


我们将讨论React Fiber体系结构的核心。 与开发人员以前认为的相比,Fiber是应用程序上的较低层抽象。 如果您认为尝试去理解它是没有希望的,请不要灰心(您并不孤单)。 继续寻找,它将最终结出果实。


等等!


我们已经实现了光纤架构的主要目标-让React利用规划的优势。 具体来说,我们需要能够:


  • 停止工作,然后再返回。
  • 优先处理不同类型的工作。
  • 重用之前完成的工作。
  • 如果不再需要,则取消该工作。

为此,我们首先需要将工作划分为多个单元。 从某种意义上说,这是纤维。 纤维代表工作单位。


更进一步,让我们回到React的基本概念“组件作为函数数据” ,通常表示为:


v = f(d) 

因此,呈现React应用程序就像调用一个函数,该函数的主体包含对其他函数的调用,依此类推。 当考虑光纤时,这种类比很有用。


计算机基本上检查程序执行顺序的方式称为调用堆栈。 功能完成后,新的堆栈容器将添加到堆栈中。 该堆栈容器表示功能完成的工作。


使用用户界面时,立即要做太多工作,这是一个问题,它可能导致动画跳动,并且会间歇出现。 此外,如果将其替换为最新更新,则某些工作可能不是必需的。 在这一点上,用户界面和功能之间的比较是不同的,因为与一般功能相比,组件具有更具体的责任。
最新的浏览器和React Native实现了有助于解决此问题的API:
requestIdleCallback分配任务,以便在一个简单的时间段内调用低优先级的函数,而requestAnimationFrame分配任务,以便在下一帧中调用高优先级的函数。 问题是要使用这些API,您需要将渲染工作分成增量单位。 如果仅依赖调用堆栈,则工作将继续进行直到堆栈为空。


如果我们可以自定义调用堆栈的行为来优化用户界面部分的显示,那不是很好吗? 如果可以中断调用堆栈以手动操作容器,那会很好吗?


这是React Fiber的调用。 Fiber是为React组件量身定制的新堆栈实现。 您可以将单根光纤视为虚拟堆栈容器。


该堆栈实现的优点是,您可以将容器堆栈保存在内存中,然后在所需的位置(以及位置)执行。 这是实现计划目标的关键定义。


除了进行计划外,通过堆栈进行的手动操作还揭示了诸如一致性(并发)和错误处理(错误边界)之类的概念的潜力。


在下一节中,我们将研究纤维的结构。


纤维结构


具体来说,“纤维”是一个JavaScript对象,其中包含有关组件及其输入和输出的信息。


纤维与堆叠容器一致,但也与组件的本质一致。


以下是“光纤”的一些重要属性(此列表并不详尽):


类型和键


类型和键用于光纤以及React元素。 实际上,创建光纤时,这两个字段将直接复制到该光纤。


光纤的类型描述了它所对应的组件。 对于组件的组成,类型是组件的功能或类别。 对于服务组件(div,span),类型为字符串。


从概念上讲,类型是一个函数,其执行由堆栈容器跟踪。


与类型一起使用时,在比较树以确定是否可以重用光纤时使用密钥。


孩子和兄弟姐妹

这些字段指向其他纤维,描述了纤维的递归结构。


纤维子项对应于在组件上调用render方法后返回的值。 在下面的示例中:


 function Parent() { return <Child /> } 

父级纤维子级对应于子级。


如果渲染返回多个子项(Fiber中的一项新功能),则使用相对(或邻居)字段:


 function Parent() { return [<Child1 />, <Child2 />] } 

子级纤维是一个单链表,其头是第一个子级。 因此,在此示例中,子Parent是Child1,而Child1的亲戚是Child2。


回到函数的类比,您可以将子光纤视为最后调用的函数(称为尾函数)。


维基百科示例:


 function foo(data) { a(data); return b(data); } 

在此示例中,被调用函数为b。


返回值(return)


返回光纤是程序在处理当前光纤之后应返回的光纤。 这与返回堆栈容器的地址相同。
它也可以被认为是母纤维。


如果光纤具有多个子光纤,则每个子光纤的返回将返回父光纤。 在上面的示例中,Child1和Child2的返回光纤是Parent。


当前和缓存的属性(pendingProps和memorizedProps)


从概念上讲,属性是函数参数。 当前的光纤属性是执行开始时的一组这些属性,缓存的属性是执行结束时的一组。


缓存输入等待属性时,这意味着先前的光纤输出可以重复使用而无需任何计算。


当前工作的优先级(pendingWorkPriority)


光纤显示优先级确定工作量。 React ReactPrioritylevel中的优先级模块包括不同的优先级及其代表的内容。


从NoWork类型的例外(0)开始,更高的数字定义最低优先级。 例如,您可以使用以下功能来检查光纤优先级是否大于指定级别:


 function matchesPriority(fiber, priority) { return fiber.pendingWorkPriority !== 0 && fiber.pendingWorkPriority <= priority } 

此功能仅用于说明目的; 它不是React Fiber数据库的一部分。


调度程序使用优先级字段来查找可以执行的下一个工作单元。 我们将在下一部分中讨论此算法。


备用(或一对)


更新(刷新)光纤-这意味着在屏幕上显示其输出。


开发中的光纤(在建)-尚未构建的光纤; 换句话说,它是尚未返回的堆栈容器。


在任何时候,组件的本质对于光纤而言都不会超过两个状态,这对应于:处于当前状态的光纤,更新的光纤或正在开发的光纤。


当前的光纤之后是正在开发的光纤,然后依次更新了光纤。


下一个纤维状态是使用cloneFiber函数延迟创建的。 几乎总是在创建新对象时,cloneFiber将尝试重用光纤的另一种(对)(如果存在),同时将资源成本降至最低。


您应该将Steam字段(或替代字段)视为实现细节,但是它在文档中如此频繁地弹出,以至于根本不可能不提及它。


结论是一个服务元素(或一组服务元素); 叶子节点React应用程序。 它们特定于每种显示环境(例如,在浏览器中为'div','span'等)。 在JSX中,它们表示为小写标记名称。


总结:我建议尝试使用新的React v16.0架构的功能

Source: https://habr.com/ru/post/zh-CN444276/


All Articles