React Fiber 中文文档

React Fiber 结构

介绍

React Fiber 是对React核心算法的重新实现,也是React团队花了两年时间研究的结晶。

React Fiber 的目标是提升其对于动画、布局、手势等场景的适用性。它的核心功能就是增量渲染:一种将渲染工作分解为多个区块并将其分散到每一帧里面。

其他核心功能还包括随着程序中新的update引起的暂定、终止和继续等;以及为不同任务分配优先级;和最新的并发性。

关于文档

Fiber引入了一些比较新的概念,所以仅通过代码很难理解。这片文档最开始是我在React项目中实现Fiber的笔记的集合。而随着它的发展,我也逐渐意识到这些笔记对其他人也可能是比较有用的资源。

我尽量通过最简单的语言,并明确定义关键术语来避免太专业化解释。在需要的时候还会有大量的外部链接来解释。

请注意我并不是React团队中的一员,所以这篇文章也并非官方权威文档,但是我也找了React团队的的成员对其准确性做了审查。

这也还是一个正在进行的工作。Fiber还在持续演变,在完成之前都有可能进行很大的改动。设计文档也会同步在这里更新,也欢迎大家来指正。

我的目标是你读完本篇文章之后对Fiber有足够的了解,以便跟上后续的演变,甚至为React作出贡献。

先决条件

强烈建议在读本文之前理解一下概念:

  • React 组件 - "Component" 是非常重要的术语,需要牢牢掌握。
  • Reconciliation - 对React reconciliation算法的高级描述。
  • React Basic Theoretical Concepts - 对没有实现负担的的React的概念描述. 刚开始的时候可能很多东西没什么意义,但是随着阅读深入会更有意义。
  • React Design Principles -需要特别关注scheduling部分。他很好的解释了为什么会有React Fiber。

review

请在此确认理解了之前的先觉条件。

在深入文档之前,先了解几个重要的概念。

什么是reconciliation?

reconciliation

React用来对比两颗虚拟DOM树的算法,以判断哪些DOM需要更新。

update

用于呈现React程序中的更改,一般是setState引发的,最终重新渲染DOM。

React的核心思想是考虑全局更新,这让开发人员可以只考虑申明式来编写代码,而不需要考虑中间过程。例如(A到B,B到C,C到A等等)。

事实上,有一点改动就更新整个app只存在于很少一部分app,而在真实的场景,这是很浪费性能的。React对此做了优化,可以保证性能的同时做到完整更新,这就是reconciliation 的部分功能。

reconciliation是虚拟DOM背后的核心算法,高级一点的描述是:渲染React程序的时候,将相应的节点树保存在内存中。然后将该节点树刷新到渲染环境中--例如浏览器,它会转化成一系列的DOM操作。当程序更新的时候(通常是setState),会重新生成一颗新的树。通过两个节点树的比较来计算这些更新需要哪些操作。

尽管Fiber是对reconciliation的重写,但是React文档中的高级算法的描述大致相同,主要在于:

  • 假如不同组件类型生成不同的树,react不会区分他们,二是直接替换。
  • list主要用key来做区分,这要求key必须“稳定、可靠并且唯一”。

Reconciliation 和render

DOM只是react可以渲染的环境之一,其他的还有通过react-native实现iOS和Android视图的渲染(所以说虚拟DOM用词很不恰当)。

react之所以能做到这一点是因为react在设计的时候就已经考虑到了将Reconciliation和render分开。reconciler负责计算树的更新,而render根据这些信息来负责应用程序的更新。

这种分离意味着React DOM 和React Native 可以保证使用React Core提供的相同的reconciler而且使用不同的渲染方法。

Fiber重新实现了reconciler , 尽管渲染方法需要重新修改以支持(并利用)新的体系结构,但它基本上与渲染无关。

Scheduling

Scheduling

确定何时work的过程

work

必须执行的任何计算,work一般是指更新的结果(例如setState)。

React的 Design Principles 这篇文档在这个主题上非常出色,这里引用一下:

在当前的实现中,react递归的遍历树,并在单个任务中调用整个更新后的render函数,但是以后可能会考虑延迟一些更新,以免丢帧。

这是React 模式中常见的主题,一些流行的库实现了“push”方法,在有新数据更新的时候可用。但是React坚持使用“pull”方法,这可以将计算延迟到需要的时候。

React不是一个常规的数据处理库,而是用于构建用户界面的库。而我们认为它在应用程序中唯一的作用就是计算什么相关,什么不相关。

如果有些东西不在当前界面,我们可以延迟跟这个相关的逻辑。如果数据量太大,导致可能丢帧,我们会批量更新。我们可以将用户交互(比如按钮点击引起的动画)优先于次要的后台工作(例如网络请求加载的新内容),以避免丢帧。

关键点在于:

  • 在用户界面中,不必立即应用每个更新。事实上,这样很浪费性能,还会导致帧下降降低用户体验。
  • 不同类型的更新有不同的优先级:动画更新优先于数据存储的更新。
  • 基于“push”的程序要求程序(你,就是你)决定如何安排工作,而基于“pull”的模式(react)会更加智能,并帮你决定。

当前React并没有充分利用Scheduling的优势,一次更新会导致立刻重新渲染整个子树。所以Fiber背后的思想就是彻底革新整个核心算法以充分利用Scheduling的优势。


现在我们开始继续深入Fiber的实现,后面的内容会更加“技术”,请确保以上的内容你已经理解。

什么是Fiber

下面将讨论React Fiber体系结构的核心,Fiber是比开发人员想象的低得多的抽象层。如果你发现你理解不了,别沮丧,继续坚持下去,最终肯定能理解。(等你理解的时候可以对本篇文章提点建议)

Here we go。


现在已经确定,Fiber的主要目标是利用React的Scheduling的优势,具体来说需要满足以下几点:

  • 暂停工作,并回来
  • 为不同类型任务分配优先级
  • 重用以前完成的工作
  • 不需要的时候终止任务

为了做到这一点,我们需要一种将工作分解成多个单元的方法。从某种意义上来说,这就是Fiber。Fiber是一个最小工作单元

为了更近一步,我们可以回顾一下 React components as functions of data, 通常表示为:

v = f(d)

因此,呈现整个react程序类似于调用一个函数,该函数的主体有屌用其他函数,以此类推。所以在思考Fiber的时候,这种类比会很有帮助。

计算机通常使用调用堆栈来跟踪程序执行的方式,一个函数被调用的时候,一个新的stack frame被添加到堆栈中,这个stack frame也代表了这个函数的工作也被执行了。

在处理UI的时候,较大的问题在于如果一次性执行太多任务,会导致动画掉帧并显得断断续续。而且,如果最新的更新取代了某些工作,则会显得不太必要,因为与常规的功能相比,组件的关注点会更多一点。

较新的浏览器(和React Native)实现了有助于解决此确切问题的API:equestIdleCallback安排在空闲期间调用的低优先级函数,而requestAnimationFrame安排在下一个动画帧上调用的高优先级函数。问题在于,想要使用这些API,你需要一种将render分解为增量单位的方法,否则会一直执行到堆栈为空。

如果我们可以自定义调用堆栈的行为来优化UI展现,是不是特别棒?如果我们可以随意调用堆栈,并且手动操作堆栈,是不是更棒?

这就是React Fiber的目标,Fiber是堆栈的重新实现,也可以将其视为虚拟堆栈

重新实现堆栈的优势在于,你可以将堆栈保存在内存中,并根据需要(以及任何时候)执行他们,这对于我们的目标来说至关重要。

除了任务调度之外,手动处理堆栈还可以释放并发和错误边界等功能。这些主题在以后的章节中会介绍。

下一节中,我们更多的来研究一下Fiber的结构。

Fiber的结构

注意:随着我们对实现细节的更加具体化,某些事项改变的可能性也增加了,如果发现任何错误或者过时的信息,请提交PR。

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

Fiber类似于一个堆栈框架,但也对应于一个组件的实例。

这里有一些Fiber的重要概念(包括但不限于)

typekey

在Fiber中,type和key的作用相同,就像React组件一样(事实上,创建一个元素的时候,Fiber会复制这两个字段)。

Fiber的type描述了他对一个的组件,对于复合组件,type是函数或者类组件本身,对于标准组件(例如div,span),type是string。

从概念上来讲,fiber是由堆栈框架执行的函数(例如v = f(d))。

与type一起,key主要用来在reconciliation期间确定Fiber是否可重用。

child and sibling

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

child fiber对应组件的render的返回值,所以在下面代码中,Parent的fiber指向Child

function Parent() {
  return <Child />
}

sibling字段说明了返回多个子项的情况(Fiber中的新功能)。

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

child fiber在这种情况形成了一个一维列表,开头是第一个子链。所以在示例中,Parent的child是Child1,而Child1的兄弟节点是Child2

回到之前的函数类比, 你可以把 child fiber当成 尾部函数

return

return fiber 是当程序处理完当前fiber之后返回的fiber。从概念上讲,它与堆栈帧的返回地址相同,也可以将其视为父fiber。

如果一个fiber具有多个子fiber,那么每个子fiber返回的都是其父fiber。因此上面的例子中Child1Child2的 return fiber都是Parent

pendingPropsmemoizedProps

从概念上来讲,props是函数的参数,fiber的pendingProps在执行开始时设置,而memoizedProps在结束的时候设置。

当传入的pendingPropsmemoizedProps相同的时候,表示fiber可以重新使用之前的fiber,以避免重复的工作。

pendingWorkPriority(待处理的工作优先级)

Fiber的工作优先级用数字来表示,翻阅 ReactPriorityLevel模块可以查询每个值所代表的含义。

除NoWork为0外,数字越大表示优先级越低。例如,您可以使用以下功能来检查Fiber的优先级是否至少与给定级别一样高:

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

这只是一个说明示例,并非Fiber代码库的一部分

scheduler使用优先级字段来查找下一个需要执行的单元,这个算法后面会讨论。

alternate

flush

fiber的flush是指将其输出渲染到屏幕上。

work-in-progress

未完成的fiber,也就是为返回的堆栈帧。

在任何时候,一个组件实例最多对应两个fiber:当前的flushed fiber 和 work-in-progress fiber。

当前fiber的备用fiber是work-in-progress fiber,反过来也是一样。

fiber的备胎由cloneFiber延迟创建,而且并非每次都创建一个新的对象,cloneFiber会尝试重用fiber的备胎(如果有的话),从而最大程度的减少资源消耗。

alternate字段可以理解成实现细节,但是在库中经常出现,所以在这里说明一下也是很有意义的。

output

host component

React程序中的叶节点,一般是指特定的渲染环境(例如在浏览器中就是div ,span等),在jsx中都是用小写标签名来表示。

fiber的输出一般都是函数的返回值。

每个fiber都会有output,但是output只会由host component在叶节点中创建,并最终输出到节点树上。

output是最终提供给渲染器的输出,以便输出到具体渲染环境(译者注:react-dom 或者 react-native)中,如何定义输出和输入是渲染器的责任。

Future sections(未来规划)

目前只说这么多,但是这片文档还远远不够完整,以后的部分会描述在整个生命周期中使用的算法,涵盖的主题包括以下:

  • scheduler如何找到下一个需要执行的工作单元
  • 如何通过fiber树来跟踪和传播优先级
  • scheduler怎么知道什么时候暂停或者继续
  • 如何刷新工作并标记为完成
  • side-effects (例如生命周期)如何运作
  • coroutine是什么以及如何用于实现上下文和布局等功能。

Related Videos

  • [What's Next for React (ReactNext 2016)](

本文为原创文章,转载请保留原出处。原文地址:https:/eatong.cn/blog/14

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352