React fiber 架构

这是我翻译的
React Fiber Architecture

介绍

React Fiber 是对 React 核心算法的重新实现

它的目的是让渲染更加流畅

Fiber 最重要的特性是可以 增量渲染(incremental rendering)

  • 可以把渲染事务(rendering work)拆分成多个块(chunk),并且把这些 chunk 分布到多个帧(frame)去完成

其他关键特性包括:

  • 有新的更新时,停止、取消、或者重用 work 的功能
  • 为不同类型的更新设置优先级
  • and new concurrency primitives

必要的知识

强烈建议在继续阅读本文之前学习以下前置知识:

reconciliation

React API 的中心思想是,让开发者只关注数据的显示,和数据状态的处理逻辑,而不用关注如何有效的把数据从一个状态变为另一个状态

一个 app 每次更改都需要重新渲染,这是非常消耗性能的,为此,React 做了很多优化,这些优化当中,大部分属于 reconciliation 的过程

现在来说下 reconciliation

reconciliation 是协调算法的意思。通常理解为 “virtual DOM” 背后的算法,大概是这样:

当一个 React 应用程序渲染的时候,会生成一个用来描述当前 app 的一个树形结构,这个“树”保存在内存中。然后这个“树”会在渲染环境刷新 - 举个例子:

在浏览器环境中,reconcilier 表示为一些 DOM 操作,当 app 被更新的时候(通常通过setState)会生成一个新的“树”。这个新的“树”会被拿去和之前的“树”做比较来确定需要哪些操作来更新当前的 APP

尽管 Fiber 彻底重写了 reconciler(协调器),但是主要思想是一样的,最重要的两点是:

  • 不同类型的组件会生成不同的树,React 将不会比较它们,而是直接用新的完全替代旧的
  • 列表的区分是使用 key 作为标识,key 应该是稳定,可预测,并且是唯一的

reconciliation VS rendering

DOM 仅仅是 React 可以渲染的环境之一,另外,React Native 也可以在 native iOS 和 Android views 环境渲染(这也是为什么我们常说的 “virtual DOM” 有点用词不当的原因)

React 支持在不同的宿主环境渲染是因为:React 把 reconciliation 和 rendering 设计成两个独立的阶段

  • reconciler 的工作是计算出“树”中的哪些部分已经发生了改变
  • 然后,renderer 通过 reconciler 计算出来的更新信息,将更新应用到 app 上

React 这种将 reconciliation 和 renderer 分开的设计,意味着,React DOM 和 React Native 可以使用他们自己的 renderer,共享 React core 提供的相同的 reconciler

Fiber 重写了 reconciler,虽然 rendering 做了一些更改来适应新的架构,但是 Fiber 主要的关注点是 reconciler

Scheduling(调度)

scheduling: 这是个进程,这个进程可以决定在什么时间点执行什么 work

work: 指需要执行的计算,work 通常是 update 的结果(e.g. setState

React 的 Design Principles 对这方面做了详细的解释:

In its current implementation React walks the tree recursively and calls render functions of the whole updated tree during a single tick. However in the future it might start delaying some updates to avoid dropping frames.
This is a common theme in React design. Some popular libraries implement the "push" approach where computations are performed when the new data is available. React, however, sticks to the "pull" approach where computations can be delayed until necessary.
React is not a generic data processing library. It is a library for building user interfaces. We think that it is uniquely positioned in an app to know which computations are relevant right now and which are not.
If something is offscreen, we can delay any logic related to it. If data is arriving faster than the frame rate, we can coalesce and batch updates. We can prioritize work coming from user interactions (such as an animation caused by a button click) over less important background work (such as rendering new content just loaded from the network) to avoid dropping frames.
This is a common theme in React design. Some popular libraries implement the "push" approach where computations are performed when the new data is available. React, however, sticks to the "pull" approach where computations can be delayed until necessary.
React is not a generic data processing library. It is a library for building user interfaces. We think that it is uniquely positioned in an app to know which computations are relevant right now and which are not.
If something is offscreen, we can delay any logic related to it. If data is arriving faster than the frame rate, we can coalesce and batch updates. We can prioritize work coming from user interactions (such as an animation caused by a button click) over less important background work (such as rendering new content just loaded from the network) to avoid dropping frames.

关键点是:

  • 在 UI 层面,没有必要每个更新都需要立刻被执行,如果每个更新都被执行,会导致丢帧,降低用户体验
  • 不同类型的更新有不同的优先级 - 动画的更新需要比数据的更新更快的完成
  • 一个 push-based 方法要求程序员去决定如何调度 work。一个 pull-based 方法允许框架(React)去决定如何调度 work

目前 React 并没有充分利用 scheduling,在一个 sub-tree 中,一个更新的结果会导致整个 sub-tree 重新 render

而改造 React 的核心算法,合理的利用 scheduling 是 Fiber 的宗旨

什么是 fiber

现在从技术层面介绍 React Fiber 架构

Fiber 主要的目的是让 React 能够更好的利用 scheduling,为此,我们需要它:

  • 可以停止 work,并且可以返回到停止的 work
  • 可以给 work 制定优先级
  • 可以复用之前已经完成的 work
  • 中断不需要的 work

为了做到这些,首先我们需要把 work 拆分成 unit。在某种意义上来说,unit 就是 fiber。一个 fiber 就代表一个 unit 的 work

为了了解的更详细一点,让我们回到 React components as functions of data,通常表示为

v = f(d)

它表示,渲染 React 应用程序类似于调用一个函数,该函数的主体包含对其他函数的调用。以此类推,这个类比在我们思考 fiber 的时候很有帮助

计算机通常使用 call stack 来追踪程序的执行。当一个函数被执行的时候,一个新的 stack frame 被添加进 stack。这个 stack frame 代表这个函数正在执行的 work

当处理 UI 的时候,不能一次性处理太多的工作,如果处理太多会出现掉帧的情况。另外,如果更新的比较频繁的话,有一些 work 是不必要的,这就是 UI 组件和函数之间不同的地方,因为组件比一般的函数具有更多的特定关注点

比较新的浏览器(和 React Native)实现了一些 API 来解决这些问题:
requestIdleCallback 会安排低优先级的任务在空闲的时候被调用,requestAnimationFrame 会在下一帧动画上调用高优先级的任务。问题是,如果你使用这些 API,你需要一个方法将渲染的 work 分解成 incremental units(增量单元)。如果你只遵循调用栈的操作方式,它会一直工作直到栈为空

React Fiber 的目的有两个:

  • 自定义调用堆栈来优化渲染
  • 随意中断调用堆栈,并且手动操作

Fiber 是专门为 React 组件实现的堆栈重构。你可以把单个 fiber 当作一个 虚拟的堆栈帧(virtual stack frame)

把堆栈重新实现一遍的好处是:

  • 可以把堆 virtual stack frame 保存在内存中,然后执行它们(任何时候)
  • 手动处理堆栈帧还可以释放并发和错误边界等功能

fiber 的结构

具体来说,fiber 是一个 javascript 对象

也就是说,一个 fiber 对应了一个 virtual stack frame,同时也对应了一个组件的实例

下面是 fiber 的一些重要字段

typekey

Fiber 的 typekey 的作用和 React 元素一样。(实际上,一个 fiber 从组件创建时,这两个字段会直接复制过来)

fiber 的 type 描述了它对应的组件

  • 对于复合组件,type 是 function 或者 class 本身。
  • 对于原生组件(div,span 等),type 为字符串

从理论上来说,type 是执行时被堆栈跟踪的函数

除了 type 以外,key 是在 reconciliation 中决定 fiber 是否可以被重用

childsibling

这些字段对应其他的 fiber,描述 fiber 树的递归结构

child fiber 代表组件的 render() 方法返回回来的值。比如在下面的例子中 Parent 的 child fiber 就是 Child

function Parent() {
  return <Child />
}

sibiling fiber 描述 render() 方法返回多个节点的情况(这是 Fiber 中的新特性):

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

在这个例子中,child fibers 是一个 head 指针式第一个 child 的单链表

Parent 的第一个 child 是 Child1,Child1 的 sibling 是 Child2

你可以吧 child fiber 想象成一个尾递归

return

当前的 fiber 执行完之后会 return 一个 fiber

从概念上来说,和返回的 stack frame 的地址是一样的

return 的 fiber 也可以看作是 parent fiber

如果一个 fiber 有很多 child fibers,每一个 child fiber 返回的 fiber 都可以看作是 它的 parent fiber

所以在上个例子中 Child1 和 Child2 的 return fiber 都是 Parent

pendingPropsmemoizedProps

一般来说, props 代表一个 function 的参数,一个 fiber 的 pendingProps 在执行开始的时候设置,memoizedProps 在结束的时候设置

当传入的 pendingProps 等于 memoizedProps 的时候,它表示可以重用 fiber 的前一个输出,避免不必要的工作

pendingWorkPriority

这是个数字,代表 fiber 工作的优先级。ReactPriorityLevel 这个 module 列出了不同的优先级和他们代表什么

0 表示 NoWork,一个很大的数字代表一个低的优先级。举个例子,你可以使用下面这个函数来检查一个 fiber 的优先级是否比给出来的 level 高

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

上面这段代码不属于 Fiber 的源码

scheduler 通过查找优先级的方式来决定下一个要执行的 work unit

alternate

flush

  • flush fiber 是将其输出到屏幕上

work-in-process

  • 还没有完成的 fiber。通常来说表示一个没有返回的 stack frame

在任何时候一个组件实例都有两个与它对应的 fiber,current fiber 和 work-in-process fiber

current fiber 和 work-in-process fiber 是交替进行的

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

推荐阅读更多精彩内容

  • 前言 React Fiber 不是一个新的东西,但在前端领域是第一次广为认知的应用。几年前全新的Fiber架构让刚...
    这个前端不太冷阅读 5,515评论 2 8
  • 背景 前段时间准备前端招聘事项,复习前端React相关知识;复习React16新的生命周期:弃用了componen...
    萧强阅读 2,215评论 0 2
  • 背景 前段时间准备前端招聘事项,复习前端React相关知识;复习React16新的生命周期:弃用了componen...
    GC风暴阅读 560评论 0 1
  • > 本文重点:介绍React重构的起因和目的,理解Fiber tree单向链表结构中各属性含义,梳理调度过程和核心...
    intopiece_槟阅读 1,202评论 0 0
  • eact框架的出现,意味着前端进入了一个新的时代。 作为后端,开始做前端的相关项目以来已经有段时间,刚开始使用Re...
    离开North阅读 2,826评论 0 4