通过 React15 ~ 17 的优化迭代来简单聊聊 Fiber

本文最后更新于:2022年3月22日 晚上

🧨 大家好,我是 Smooth,一名大二的 SCAU 前端er
🏆 本篇文章是我在复习 Fiber 时,查看了多篇文章之后,结合成的精华
🙌 如文章有误,恳请评论区指正,谢谢!
❤ 写作不易,「点赞」+「收藏」+「转发」 谢谢支持!

原文章链接

React渲染页面的两个阶段

首先,明确两个阶段,有助于后面对文章的理解

  1. 调度阶段(reconciliation):在这个阶段 React 会更新数据生成新的 Virtual DOM,然后通过Diff算法,快速找出需要更新的元素,放到更新队列中去,得到新的更新队列
  2. 渲染阶段(commit):这个阶段 React 会遍历更新队列,将其所有的变更一次性更新到DOM上

React 15 架构

React15架构可以分为两层:

  • Reconciler(协调器)—— 负责找出变化的组件;
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上;

  在React15及以前,Reconciler采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,递归更新时间超过了16ms,用户交互就会卡顿。

  为了解决这个问题,React16将递归的无法中断的更新重构为异步的可中断更新,由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是,全新的 Fiber 架构应运而生。

React 16 架构

  为了解决同步更新长时间占用线程导致页面卡顿的问题,也为了探索运行时优化的更多可能,React开始重构并一直持续至今。重构的目标是实现Concurrent Mode(并发模式)。

  从v15到v16,React团队花了两年时间将源码架构中的Stack Reconciler重构为Fiber Reconciler。

React16架构可以分为三层:

  • Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler;
  • Reconciler(协调器)—— 负责找出变化的组件:更新工作从递归变成了可以中断的循环过程。Reconciler内部采用了Fiber的架构
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上。

React 17 优化

  React16的expirationTimes模型只能区分是否>=expirationTimes决定节点是否更新。React17的lanes模型可以选定一个更新区间,并且动态的向区间中增减优先级,可以处理更细粒度的更新。

Lane用二进制位表示任务的优先级,方便优先级的计算(位运算),不同优先级占用不同位置的“赛道”,而且存在批的概念,优先级越低,“赛道”越多。高优先级打断低优先级,新建的任务需要赋予什么优先级等问题都是Lane所要解决的问题。

Concurrent Mode的目的是实现一套可中断/恢复的更新机制。其由两部分组成:

  • 一套协程架构:Fiber Reconciler
  • 基于协程架构的启发式更新算法:控制协程架构工作方式的算法

什么是 Fiber

  Fiber 的英文含义是“纤维”,它是比线程(Thread)更细的线,比线程(Thread)控制得更精密的执行模型。在广义计算机科学概念中,Fiber 又是一种协作的(Cooperative)编程模型(协程),帮助开发者用一种【既模块化又协作化】的方式来编排代码。

  在 React 中,Fiber 就是 React 16 实现的一套新的更新机制,让 React 的更新过程变得可控,避免了之前采用递归需要一气呵成影响性能的做法

React Fiber 中的时间分片

  把一个耗时长的任务分成很多小片,每一个小片的运行时间很短,虽然总时间依然很长,但是在每个小片执行完之后,都给其他任务一个执行的机会,这样唯一的线程就不会被独占,其他任务依然有运行的机会。

  React Fiber 把更新过程碎片化,每执行完一段更新过程,就把控制权交还给 React 负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务。

Fiber Reconciler

链表结构

  在 React Fiber 中用链表遍历的方式替代了 React 16 之前的栈递归方案。在 React 16 中使用了大量的链表。

  • 使用多向链表的形式替代了原来的树结构;

链表相比顺序结构数据格式的好处就是:

  1. 操作更高效,比如顺序调整、删除,只需要改变节点的指针指向就好了。
  2. 不仅可以根据当前节点找到下一个节点,在多向链表中,还可以找到他的父节点或者兄弟节点。

但链表也不是完美的,缺点就是:

  1. 比顺序结构数据更占用空间,因为每个节点对象还保存有指向下一个对象的指针。
  2. 不能自由读取,必须找到他的上一个节点。

  React 用空间换时间,更高效的操作可以方便根据优先级进行操作。同时可以根据当前节点找到其他节点,在下面提到的挂起和恢复过程中起到了关键作用

你对 React Fiber 的了解?为什么要使用 Fiber?

因此,这个问题可以如下回答

对于 Fiber,首先我先说一下为什么要使用他,首先 React 更新视图分为两个大阶段,第一个是基于 Reconciler 数据结构的调度阶段,第二个是渲染阶段。在 React15 及之前的版本,调度阶段采用的是 Stack Reconciler,即使用的是栈的数据结构,在渲染阶段采用的是递归进行更新。由于 React 将视图看作是一个个函数执行的结果,而每个页面又包含多个视图,会让栈中的函数层级非常深,而递归得一次性将这些函数运行完,容易造成 16.6ms 内无法及时执行完这些渲染更新,而无法在下一帧及时将控制权交还给渲染进程,无法及时绘制下一帧造成浏览器页面卡顿。

为了让更新过程变得可控,React16 采用的是 Fiber Reconciler ,核心思想是时间分片的思想,将一个大任务分解成多个小任务,当执行完每个小任务的更新时,能将控制权交还给浏览器负责任务调度的模块,这样可以在有紧急任务时进行执行,而不用“一心一意”执行更新。Fiber Reconciler 采用的是链表结构,通过指针可以将父节点、兄弟节点、子结点连接起来,好处是可以简化更新过程,变换一下链表指针就行,劣势是得知道某个结点的上一个结点才能进行更新,是一个典型的空间换时间的方法。

对于 React17,由于 React16 只能知道某个结点是否要更新,但 React17 能通过指定某个更新区间并设置优先级进行更新,能更加准确的将 React 更新过程分为三个阶段,第一是分配优先级阶段,第二个调度阶段,第三个是渲染阶段,让 React 可以处理更细粒度的更新。

总结来说就是,Fiber 是 React16 实现的一套新的更新机制,让 React 的更新过程变得可控,避免了之前采用递归需要一气呵成影响性能的做法


版权说明:文章参考自 React Fiber很难?六个问题助你理解 React Fiber



最后

我是 Smoothzjc,致力于产出更多且不仅限于前端方面的优质文章

大家也可以关注我的公众号 @ Smooth前端成长记录,及时通过移动端获取到最新文章消息!

写作不易,「点赞」+「收藏」+「转发」 谢谢支持❤

往期推荐

《都2022年了还不考虑来学React Hook吗?6k字带你从入门到吃透》

《一份不可多得的 Webpack 学习指南(1万字长文带你入门 Webpack 并掌握常用的进阶配置)》

《(建议收藏)从 URL 输入到页面展现的全过程》

《【offer 收割机之 CSS 回顾系列】请你解释一下什么是 BFC ?他的应用场景有哪些?》

《Github + hexo 实现自己的个人博客、配置主题(超详细)》

《10分钟让你彻底理解如何配置子域名来部署多个项目》

《一文理解配置伪静态解决 部署项目刷新页面404问题

《带你3分钟掌握常见的水平垂直居中面试题》

《【建议收藏】长达万字的git常用指令总结!!!适合小白及在工作中想要对git基本指令有所了解的人群》

《浅谈javascript的原型和原型链(新手懵懂想学会原型链?看这篇文章就足够啦!!!)》8)


作者:Smooth

文章链接:http://example.com/2022/03/20/%E9%80%9A%E8%BF%87%20React15%20~%2017%20%E7%9A%84%E4%BC%98%E5%8C%96%E8%BF%AD%E4%BB%A3%E6%9D%A5%E7%AE%80%E5%8D%95%E8%81%8A%E8%81%8A%20Fiber/

版权说明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!