原文
react-fiber-architecture
介绍
React Fibre 是 React 核心算法正在进行的重新实现.它是 React 团队两年多的研究成果.
React Fiber 的目标是提高其对动画,布局和手势等领域的适用性.它的主体特征是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧中.
其他主要功能包括在进行更新时暂停,中止或重新使用工作的能力; 为不同类型的更新分配优先权的能力; 和新的并发原语.
关于这个文档
Fiber 引入了几个新颖的概念,很难通过查看代码来完成.这个文档是我们在 React 项目中随着 Fibre 实现的一系列笔记开始的.随着它的发展,我意识到它也可能成为其他人的有用资源.
我会尝试尽可能使用最普通的语言,并通过明确定义关键术语来避免行话.在可能的情况下,我也会大量连接外部资源.
请注意,我不在 React 团队,也不会从任何权威机构发言.这不是一个正式的文件.我已经要求 React 团队的成员对其进行检查以确保准确性.
这也是一个正在进行的工作.Fiber 是一个正在进行的项目,在完成之前可能会经历重大的重构.我也试图在这里记录它的设计.改进和建议是非常受欢迎的.
我的目标是在阅读本文档之后,您将会理解 Fiber 的实施情况 ,并最终甚至能够回馈给 React.
先决条件
我强烈建议您在继续之前熟悉以下资源:
React Components, Elements, and Instances - "Component" 通常是一个重载的术语.牢牢掌握这些术语至关重要.
Reconciliation - 对 React reconciliation 算法的高级描述.
React 基本理论概念 - 对 React 概念模型的描述.其中一些内容在第一次阅读时可能没有意义.没关系,随着时间的推移会更有意义.
React 设计原则 - 特别注意 scheduling 部分.它很好的解释了 React Fiber 的工作原理.
Review
如果你还没有看先决条件,请先看完它.
在我们深入研究新的东西之前,让我们回顾一下几个概念.
什么是 reconciliation
reconciliation
React算法,用来比较2颗树,以确定哪些部分需要改变.
更新
用于呈现React应用的数据更改.通常是`setState`的结果.最终导致重新渲染.
React 的 API 的核心思想认为更新会导致整个应用程序重新渲染.这允许开发人员以声明的方式进行推理,而不用担心如何有效地将应用程序从任何特定状态转换到另一个状态(从 A 到 B,从 B 到 C,从 C 到 A 等等).
实际上,在每次更改时重新渲染整个应用程序只适用于最琐碎的应用程序; 在实际的应用程序中,性能方面的代价非常高昂. React 具有优化功能,可在保持良好性能的同时创建整个应用程序需要重新呈现的外观.这些优化的大部分内容是 reconciliation 的一部分.
Reconciliation 是被普遍理解为 "虚拟 DOM" 的算法.高级描述如下所示:当您渲染 React 应用程序时,会生成一个描述应用程序的节点树并保存在内存中.然后将该树刷新到渲染环境 - 例如,在浏览器中,将其转换为一组 DOM 操作.当应用程序更新(通常通过 setState),生成一个新的树.新树与前一棵树有区别,以计算需要更新呈现的应用程序的操作.
尽管 Fibre 是对 reconciler 的彻头彻尾的重写,但 React 文档中描述的高级算法在很大程度上是相同的.关键是:
假定不同的组件类型产生实质上不同的树.React 不会试图区分它们,而是完全替换旧的树.
列表的区分使用 keys 来执行.关键应该是 "稳定,可预测,独特".
Reconciliation 与渲染
DOM 只是 React 可以渲染的渲染环境之一,还可以通过 React Native 进行本地 iOS 和 Android 视图. (这就是为什么 "虚拟 DOM" 有点用词不当).
它可以支持如此多目标是因为 React 的设计使 reconciliation 和渲染是分开的阶段.reconciler 完成了计算树的哪些部分已经改变的工作; 渲染器然后使用该信息实际更新呈现的应用程序.
这种分离意味着 React DOM 和 React Native 可以使用他们自己的渲染器,同时共享由 React 核心提供的相同的 reconciler.
Fiber 重新实现了 reconciler.虽然渲染者需要改变以支持(并利用)新的架构,但它并不主要关心渲染.
确定何时应该进行工作的过程.
Scheduling
scheduling
工作
任何必须执行的计算.工作通常是更新的结果(例如setState).
React 的 设计原则文档 在这个主题上非常好,我只是在这里引用它:
在当前实现中,React 递归地遍历树,并在单个 tick 中调用整个更新树的 render 函数.但是,将来可能会延迟一些更新以避免丢帧.
这是 React 设计中的一个常见主题.当新数据可用时,一些流行的库实现了 "push" 方法,其中计算被执行.然而,React 坚持 "pull" 的方法,计算可以延迟到必要的时候.
React 不是一个通用的数据处理库.这是一个建立用户界面的库.我们认为它是唯一定位在一个应用程序来知道现在哪些计算是相关的,哪些不是.
如果有什么东西在屏幕外,我们可以推迟任何与之相关的逻辑.如果数据比帧速率更快到达,我们可以合并批量更新.我们可以优先考虑来自用户交互的工作(比如点击按钮造成的动画)而不是重要的后台工作(比如刚刚从网络加载的新内容),以避免丢失帧.
关键是:
在用户界面中,没有必要立即应用每个更新.实际上,这样做可能会造成浪费,导致帧丢失并降低用户体验.
不同类型的更新具有不同的优先级 - 动画更新需要比从数据存储更新更快地完成.
基于 push 的方法要求应用程序(您,程序员)决定如何安排工作.基于 pull 的方法可以使框架(React)更加智能,并为您做出决定.
目前,React 并没有以重要的方式利用 scheduling; 整个子树的更新结果立即被重新渲染.检修 React 的核心算法以利用 scheduling 是 Fiber 背后的驱动理念.
现在我们已经准备好进入 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 的结构.
fiber 的结构
注意:随着我们对实现细节的更具体的了解,事情可能发生变化的可能性会增加.如果您发现任何错误或过时的信息,请提交 PR.
具体而言,fiber 是一个 JavaScript 对象,包含有关组件,其输入和输出的信息.
fiber 对应于堆栈帧,但也对应于组件的一个实例.
这是一些属于 fiber 的重要领域. (这个清单并不详尽.)
type and key
fiber 的 type 和 key 与 React 元素的作用相同. (实际上,当从一个元素创建一个 fiber 时,这两个字段直接被复制过来.)
fiber 的 type 描述了它对应的组件.对于复合组件,类型是函数或类组件本身.对于 host 组件(div,span 等),类型是一个字符串.
从概念上讲,type 是函数(如在 v = f(d)中),其执行被栈帧跟踪.
随着 type 的不同,在 reconciliation 期间使用 key 来确定 fiber 是否可以重新使用.
child and sibling
这些字段指向其他 fiber,描述 fiber 的递归树状结构.
子 fiber 对应于组件渲染方法返回的值.所以在下面的例子中
Parent 的 child fiber 对应于 Child.
function Parent() {
return < Child / >
}
兄弟领域说明了渲染返回多个 children 的情况(Fiber 中的一个新特性):
child 的 fiber 形成一个单一的链表,head 是第一个 child.所以在这个例子中,Parent 的 child 是 Child1,而 Child1 的兄弟是 Child2.
function Parent() {
return [ < Child1 / >, <Child2 / >]
}
回到我们的功能比喻,你可以把一个子 fiber 想象成一个 尾调用函数 .
return
return fiber 是程序在处理完当前 fiber 后返回的 fiber.它在概念上与堆栈帧的返回地址相同.它也可以被认为是 parent fiber.
如果 fiber 具有多个子 fiber,则每个子 fiber 的 return fiber 是 parent.所以在前面的例子中,Child1 和 Child2 的 return fiber 是 Parent.
pendingProps 和 memoizedProps
从概念上讲,props 是一个函数的 arguments.一个 fiber 的 pendingProps 在执行开始时被设置,memoizedProps 被设置在最后.
当传入的 pendingProps 等于 memoizedProps 时,它表明 fiber 的先前输出可以被重复使用,避免不必要的工作.
pendingWorkPriority
一个数字,表示 fiber 所代表的工作的优先级. ReactPriorityLevel 模块列出了不同的优先级以及它们代表的内容.
除 NoWork 为 0 外,较大的数字表示较低的优先级.例如,您可以使用以下函数来检查 fiber 的优先级是否至少与给定级别一样高:
此函数仅用于说明; 它实际上并不是 React Fiber 代码库的一部分.
function matchesPriority(fiber, priority) {
return fiber.pendingWorkPriority !== 0 &&
fiber.pendingWorkPriority <= priority
}
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 组件在叶子节点创建.然后将输出传送到树上.
输出是最终呈现给渲染器,以便它可以刷新渲染环境的变化.渲染者有责任定义输出是如何创建和更新的.
未来部分
现在就是这样,但是这个文件还远远没有完成.以后的部分将介绍在整个生命周期中使用的算法.要涵盖的主题包括:
scheduler 如何找到要执行的下一个工作单元.
如何通过 fiber 树跟踪和传播优先级.
scheduler 如何知道何时暂停和恢复工作.
工作如何被刷新并标记为完整.
副作用(如生命周期方法)如何工作.
协程是什么以及如何用它来实现上下文和布局等功能.
相关视频
What's Next for React (ReactNext 2016)
相关资料
1, 完全理解 React Fiber
2, React 16 Fiber 源码速览
3, React Fiber 是什么
4, 如何理解 React Fiber 架构?
5, Under-the-hood-ReactJS
tree 相关论文
A Survey on Tree Edit Distance and Related Problems
来源: https://segmentfault.com/a/1190000012834204