React Fiber

随着 React 16 的发布,Hooks 的正式上线,很多小伙伴都很兴奋,都想要尝试这一新的特性,升级 React 的意愿越来越强烈了。

我们都知道 React 是一个优秀的前端框架,很多的大型应用都在使用,而作为使用 React 为工具的开发者也应该了解下 React Fiber,Fiber 到底是什么?它能给我们带来什么?以及 React 团队为什么要去重写 Fiber 架构?

什么是 React Fiber

React Fiber 是 React 16 中新的协调引擎,是对核心算法的一次重新实现。

既然是新的,那 React 团队为什么要重写新的 Fiber 架构呢?

在 React 16 以前,当元素较多,需要频繁刷新的时候页面会出现卡顿,究其原因是因为更新过程是同步的,大量的同步计算任务阻塞了浏览器的渲染。

当页面加载或者更新时,React 会去计算和比对 Virtual DOM,最后绘制页面,整个过程是同步进行的。当 JavaScript 在浏览器的主线程上长期运行,就会阻塞了样式计算、布局和绘制,导致页面无法得到及时的更新和响应。此时,无论用户如何点击鼠标或者敲击键盘都不会得到响应,当 React 更新完成后刚刚点击或敲击的事件才会得到响应。

由于 JavaScript 是单线程的特点,所以一个线程执行完成后才会执行下一个线程,当上一个线程任务耗时太长,程序就会对其他输入不作出响应。

React 的更新过程会先计算,一旦任务开始进行,就无法中断, js 将一直占用主线程, 直到整棵 Virtual DOM 树计算完成之后,才能把执行权交给渲染引擎,而 React Fiber 就是要改变现状。

React 16 以前的更新过程
它能给我们带来什么?
  • 增量渲染

  • 为不同任务分配优先极

  • 更新时能暂停、终止、复用渲染任务

  • 并发方面新的能力

接下来,让我们具体来了解下 Fiber。

Fiber

Fiber 把耗时长的任务拆分成很多的小片,每个小片的运行时间很短,每次只执行一个小片,执行完后看是否还有剩余时间,如果有就继续执行下个小片,如果没有就挂起当前任务,将控制权交给 React 负责任务协调的模块,看有没有其他紧急任务要做,如果没有就继续更新当前任务,如果有紧急任务就去做紧急任务,等主线程不忙的时候在继续执行当前任务。

分片之后,每执行一段时间,都会将控制权交给主线程。

这样唯一的线程不会被独占,其他任务依然有运行的机会。

这种策略叫做 Cooperative Scheduling(合作式调度),操作系统常用任务调度策略之一。

总而言之,我们了解到,Fiber 是一个最小工作单元,也是堆栈的重新实现,可以理解为是一个虚拟的堆栈帧。它将可中断的任务拆分成多个任务,通过优先级来自由调度子任务,分段更新,从而将之前的同步渲染改为异步渲染。

Fiber 结构

维护每一个分片的数据结构,就是 Fiber,它可以用 JS 对象来表示,其中包含有关组件,以及输入和输出的信息:

const fiber = {
  // 跟当前 Fiber 相关本地状态(比如浏览器环境就是 DOM 节点)
  stateNode: any, // 节点实例
  // 指向他在 Fiber 节点树中的 `parent`,用来在处理完这个节点之后向上返回
  return: Fiber | null, // 父节点
  // 单链表树结构
  child: Fiber | null, // 指向自己的第一个子节点
  sibling: Fiber | null, // 指向自己的兄弟结构,兄弟节点的return指向同一个父节点
  index: number,

  // 组件相关
  tag: WorkTag, // 标记不同的组件类型
  key: null | string, // ReactElement 里面的 key,与 type 一起,主要用来在reconciliation 期间确定 Fiber 是否可重用。
  elementType: any, // ReactElement.type,也就是我们调用 `createElement` 的第一个参数
  type: any,  // 对于复合组件,type 是函数或者是类组件(`function`或者`class`),对于标准组件(div或者span),type 是 string
    
  // ref属性
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,

  // 更新相关
  // 当传入的 pendingProps 和 memoizedProps 相同的时候,表示 fiber 可以重新使用之前的 fiber,以避免重复的工作。
  pendingProps: any,  // 新的变动带来的新的props
  memoizedProps: any,  // 上一次渲染完成之后的props
  updateQueue: UpdateQueue<any> | null,  // 该Fiber对应的组件产生的Update会存放在这个队列里面
  memoizedState: any, // 上一次渲染的时候的state
  firstContextDependency: ContextDependency<mixed> | null, // 一个列表,存放这个Fiber依赖的context
  pendingWorkPriority: number, // 待处理的工作优先级,除 NoWork 为0外,数字越大表示优先级越低。
    
  // Scheduler 相关
  expirationTime: ExpirationTime,  // 代表任务在未来的哪个时间点应该被完成,不包括他的子树产生的任务
  // 快速确定子树中是否有不在等待的变化
  childExpirationTime: ExpirationTime,

  // 用来描述当前Fiber和他子树的`Bitfield`
  // 共存的模式表示这个子树是否默认是异步渲染的
  // Fiber被创建的时候他会继承父Fiber
  // 其他的标识也可以在创建的时候被设置
  // 但是在创建之后不应该再被修改,特别是他的子Fiber创建之前
  mode: TypeOfMode,
    
  // 在Fiber树更新的过程中,每个Fiber都会有一个跟其对应的Fiber
  // 我们称他为`current <==> workInProgress`
  // 在渲染完成之后他们会交换位置
  alternate: Fiber | null,

  // Effect 相关的
  effectTag: SideEffectTag, // 用来记录Side Effect
  nextEffect: Fiber | null, // 单链表用来快速查找下一个side effect
  firstEffect: Fiber | null,  // 子树中第一个side effect
  lastEffect: Fiber | null, // 子树中最后一个side effect
}
任务的优先级

上面讲到了任务的执行是根据优先级来调度的,那我们现在具体了解一下优先级。

  • synchronous 同步执行,首屏使用

  • task 在 next tick 之前执行

  • animation 下一帧之前执行,通过requestAnimationFrame来调度,这样在下一帧就能立即开始动画过程

  • high 在不久的将来立即执行

  • low 稍微延迟(100-200ms)执行也没关系

  • offscreen 当前隐藏的、屏幕外的(看不见的)元素,在下一次 render 时或 scroll 时才执行

Fiber reconciler

Fiber Reconciler 决定了当任务调度完成之后,如何去执行每个任务,如何去更新每一个节点的过程。

React Fiber 更新过程分为两个阶段

  • 第一阶段 Reconciliation Phase,生成 Fiber 树,得出需要更新的节点信息。是一个渐进的过程,可以被打断。可能会调用 componentWillMount,componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate 生命周期函数。

  • 第二阶段 Commit Phase,将需要更新的节点批量更新,这个过程不能被打断。可能会调用 componentDidMount,componentDidUpdate,componentWillUnmount 生命周期函数。

Fiber 更新过程
Fiber tree 与 WorkInProgress Tree
// 单链表树结构
{
   return: Fiber | null, // 指向父节点
   child: Fiber | null, // 指向自己的第一个子节点
   sibling: Fiber | null, // 指向自己的兄弟结构,兄弟节点的 return 指向同一个父节点
}

首次渲染之后 React 会得到一个 Fiber 树,也就是 Current tree(当前树)。当处理更新的时候,React 会构建 WorkInProgress Tree(工作过程树),当构造完成后会将 current 指针指向 WorkInProgress Tree,WorkInProgress Tree 成了新的 Fiber tree。

这被称做双缓冲。以 Fiber tree 为主,WorkInProgress Tree 为辅。

双缓冲技术可以复用内部对象(fiber),节省内存分配、GC的时间开销。

总结

本篇文章篇幅有限,大致讲了 Fiber 的相关知识和 React 的工作流程,对于细节,比如:如何调度任务,如何 diff 等,感兴趣的同学可以自行结合源码研究分析。

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

推荐阅读更多精彩内容