React v16.x 之深入理解Fiber

众所周知,react16版本更新后,将原来的Stack Reconciler架构重写成了Fiber Reconciler架构,从而解决了React15遗留下来的整体渲染掉帧、响应延迟缓慢的问题。这里有个优化前后的例子:react-fiber-vs-stack-demo

为了更好地理解Fiber的处理思路,我们需要先了解下call stack和Scheduling的定义。

call stack调用栈

调用栈是解释器(比如浏览器中的 JavaScript 解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。

  • 每调用一个函数,解释器就会把该函数添加进调用栈并开始执行。
  • 正在调用栈中执行的函数还调用了其它函数,那么新函数也将会被添加进调用栈,一旦这个函数被调用,便会立即执行。
  • 当前函数执行完毕后,解释器将其清出调用栈,继续执行当前执行环境下的剩余的代码。
  • 当分配的调用栈空间被占满时,会引发“堆栈溢出”错误。

scheduling

scheduling :决定何时应该执行work的那部分程序

work: 需要被执行的所有计算。work一般意义下是作为一次更新的结果而存在的。

  • 在UI开发中,没有必要将每一个更新请求付诸实施。实际上,如果真的是这么做的话,那么界面就会出现掉帧的现象,这大大降低了用户体验。
  • 不同类型的更新请求应该由不同的优先级。比如,执行动画的优先级应该要比一个来自于data store的更新的优先级要高。
  • push-based的方案要求你(开发者)需要手动去调度work。而pull-based的方案则能够让框架(react)学得变聪明,然后帮你去做这些调度工作。

react15工作阶段

reconciliation VS rendering

  • reconciliation:通过比对节点树来计算出节点树中哪部分需要更改。是自顶向下的递归算法。遍历新数据生成新的虚拟DOM。并且通过diff算法来找出需要更新的元素,放在更新队列中。该过程不能被中断。
  • rendering:遍历更新队列,负责利用计算结果来做一些实际的界面更新动作。

原因: 在reconciler阶段中,由于为了找出需要更新的元素,花费来了大量的时间。所以渲染阶段被延后,导致卡顿原因。

React Fiber

React Fiber的首要目标是使得React能够整合scheduling。更具体地来说,是我们能做到一下几点:

  • 中断work和稍后恢复work
  • 对不同类型的work赋予相当的优先级
  • 复用之前已经完成的work
  • 如果一个work已经不需要继续了,中断它。
    一个React Fiber代表着一个work单元。

Fiber 在 React 生成的 Virtual Dom 基础上增加的一层链表数据结构,把递归遍历转成循环遍历。配合 requestIdleCallback , 实现任务拆分、中断与恢复。我们再来了解几个概念

requestIdleCallback
window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。

递归遍历
React16之前节点之间的关系可以用数据结构中树的深度遍历来表示。一个函数不断调用自身的行为。
基本思想: 首先从图中某个顶点v0出发,访问此顶点,然后依次从v0相邻的顶点出发深度优先遍历,直至图中所有与v0路径相通的顶点都被访问了;若此时尚有顶点未被访问,则从中选一个顶点作为起始点,重复上述过程,直到所有的顶点都被访问。可以看出深度优先遍历是一个递归的过程。
通俗的解释: 递归就像往存钱罐里存钱,先往里边塞钱,2块,5块,10块这样的塞,叫入栈。取钱的时候,后塞进去的先取出来,这叫出栈。具体多少钱,要全部出栈才知道。
在这里插入图片描述
其深度优先遍历得到的序列为: 0->1->3->7->4->2->5->6

Fiber 链表数据结构

递归遍历不能将工作分解为增量单位,React一直保持迭代,直到处理完所有组件并且堆栈为空为止。
使用单链列表树遍历算法,这样可以暂停遍历并阻止堆栈增长。链表遍历算法可以异步运行,使用指针返回到其暂停工作的节点。
在这里插入图片描述

  • 父节点到子节点(红色虚线)
  • 同层节点(黄色虚线)
  • 子节点到父节点(蓝色虚线)
  • 父节点指向第一个子节点, 每个子节点都指向父节点,同层节点间是单向链表。

它的遍历过程:

  • 从current(Root)开始通过child向下找
  • 如果有child先遍历子节点,直到null为止
  • 然后看是否有兄弟节点
  • 有兄弟节点则遍历兄弟节点
  • 然后再看兄弟节点是否有子节点
  • 然后return看父节点是否有兄弟节点
  • 直到root

var b2 = {
    name: 'b2', render : () => [c2] }
var b3 = {
    name: 'b3', render : () => [] }
var c1 = {
    name: 'c1', render : () => [d1]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值