资讯专栏INFORMATION COLUMN

React Fiber源码分析 第四篇(归纳总结)

jsdt / 712人阅读

摘要:为什么网页性能会变高要回答这个问题,需要回头看是单线程的知识点。在分析的过程中,发现了的源码中使用了很多链式结构,回调链,任务链等,这个主要是为了增删时性能比较高

系列文章

React Fiber源码分析 第一篇
React Fiber源码分析 第二篇(同步模式)
React Fiber源码分析 第三篇(异步状态)
React Fiber源码分析 第四篇(归纳总结)

前言

React Fiber是React在V16版本中的大更新,利用了闲余时间看了一些源码,做个小记录~

什么是Fiber 从开发者角度来看

实际上这次更新对于我们来说影响并不大,只是几个生命周期改变了(React在版本中的更新简直做到了像一门语言一样,完美的兼容老版本,底层算法的大重构对于开发者来说完全透明),新引入的两个生命周期函数 getDerivedStateFromProps,getSnapshotBeforeUpdate 以及在未来 v17.0 版本中即将被移除的三个生命周期函数componentWillMount,componentWillReeiveProps,componentWillUpdate,目前版本并不会影响原生命周期的使用,但不能和新的生命周期一起使用,也会被标记为不安全,下图为目前React的流程图

其他的几乎没有任何影响,我们还是照常的写着原来的代码,然后我们就感觉到网页性能更高了一些。

为什么网页性能会变高

要回答这个问题,需要回头看javascript是单线程的知识点。

单线程一次只能做一件事, 在原来的React中, 如果一次更新的时间比较长,那么用户就会感觉到卡顿,也就是丢帧了。

打个比方, 假如我现在要更新1000个组件(往大了说),每个组件平均花时间1ms,那么在1s内,浏览器的整个线程都被阻塞了,这时候用户在input上的任何操作都不会有反应,等到更新完毕,界面上突的一下就显示了原来用户的输入,这个体验是非常差的。这里借用官方一张图, Fiber之前的版本就是这样,调用栈非常深

那么Fiber,现在是怎么做呢?

Fiber实际上是把一次更新拆成一个个的单元任务,每次做完一个单元任务后,就询问是否有更高的优先级任务,有就去执行,回头再来干这件事,如图

那么就明白了,Fiber是一个任务调和器!, 同样,我们根据这个来分析Fiber具体做了什么

Fiber具体做了什么

首先,要做到这样的效果,那么就需要有以下的功能:

任务可分片 (拆分任务)

任务可中断 (执行另一个任务后, 可以回头继续执行未完成的任务)

具备优先级 (哪个任务先执行)

任务可分片

在React中,无论是state还是props的更新, 最后都操作在JSX的标签上
利用这种天然友好的表达,直接把每一个标签当成一个任务分片如:div、p1、p2、span都是一个任务分片

p1

p2

当然, 还要从标签转换成VDOM,再转成Fiber,才是一个真正的任务片,如图:

fiber的数据结构

任务可中断

Fiber之前React是通过栈调度器进行递归更新,毕竟标签化是天然嵌套的,对递归友好,但是递归不好break和continue

从大递归到大循环

Fiber则是以链表的形式来进行逐步更新(深度优先遍历算法),链表对break和continue友好Fiber节点拥有return, child, sibling三个属性,分别对应父节点, 第一个孩子, 它右边的兄弟,


(图来自网络,侵删)

如何回到中断
任务中断,执行高优先级任务后如何回来被中断的任务

React内部维护一个任务链表,每次某个任务结束后都会删除已完成的任务并继续执行其他可执行的任务,每个任务都有一个finishedWork属性,如果该属性不为null,则说明更新完毕,只差commit render阶段

回到中断任务后,如何从中断的任务片开始

这个主要依赖于fiber中的两个属性expirationTime和childExpirationTime,当某个fiber被执行完毕后,会把expirationTime设为NoWork,即被打断后可以通过该属性判断任务碎片是否
需要执行

this.expirationTime = NoWork  // 任务优先级
this.childExpirationTime = NoWork // 子任务片的优先级
任务中断再执行的流程

通过深度遍历搜索算法对每一个fiber即任务碎片进行更新

每一个任务碎片完成后会将expirationTime设为NoWork

假设此时有更高优先级的任务,则执行更高优先级任务

任务执行完成后,会从任务列表中剔除,并继续执行其他未完成且可以执行的任务。

回到被打断任务,可以通过任务的finishWork属性判断是否需要执行更新

根据任务碎片的expirationTime判断是否需要执行更新

中断更新阶段其他属性介绍
Alternater

每次更新都不会对fiber直接操作,而是克隆一个作为alternater属性

updateQueue

更新队列, 存放更新的信息

Effect

收集更新信息,生成真实DOM

具备优先级

每个Root任务更新任务fiber都具有expirationTime属性,该属性即为优先级expirationTime越小,优先级越高,同步模式下该值为0, 每个层级的任务都是以链表的形式存在

为什么采用时间作为优先级属性

这时候就是requestIdleCallback这个API的骚操作了, 这个API是干嘛的呢?

window.requestIdleCallback()会在浏览器空闲时期依次调用函数, 这就可以让开发者在主事件循环中执行后台或低优先级的任务,而且不会对像动画和用户交互这样延迟触发而且关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。

也就是说React实际上利用这个API在浏览器空闲期执行任务, 而这个API的回调有个参数deadline , 当你超时的时候,无论是不是在空闲期都会执行该任务, 这也就解释了为什么React采用时间来做优先级

不过实际上, React并没有在版本中使用了这个API,而是通过requestAnimationFrame来hack,强行设置每一帧的到期时间为requestAnimationFrame回调函数的参数加上33ms

var animationTick = function (rafTime) {
    isAnimationFrameScheduled = false;
    ...
    ...
    // 每帧到期时间为33ms
    frameDeadline = rafTime + 33
    if (!isIdleScheduled) {
      isIdleScheduled = true;
      window.postMessage(messageKey, "*");
    }
  };

当然了, 分优先级是有一个无法避免的问题, 那就是当有无数的优先级更高的任务插进来, 就会形成饥饿现象,原有的任务会一直得不到机会执行

总结

React Fiber实际上就是一个任务调和器,它做到了将每一次更新切分成任务分片,从而拥有了可中断且有优先级的进行其他任务的功能。
在分析的过程中,发现了React的源码中使用了很多链式结构, 回调链,任务链等,这个主要是为了增删时性能比较高

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101474.html

相关文章

  • React Fiber源码分析 第一篇

    摘要:系列文章源码分析第一篇源码分析第二篇同步模式源码分析第三篇异步状态源码分析第四篇归纳总结前言是在版本中的大更新,利用了闲余时间看了一些源码,做个小记录流程图源码分析先由编译,调用,入参为,打印出来可以看到,,分别代表着元素原生元素,回调函数 系列文章 React Fiber源码分析 第一篇 React Fiber源码分析 第二篇(同步模式) React Fiber源码分析 第三篇(...

    amc 评论0 收藏0
  • React Fiber源码分析 第三篇(异步状态)

    摘要:系列文章源码分析第一篇源码分析第二篇同步模式源码分析第三篇异步状态源码分析第四篇归纳总结前言是在版本中的大更新,利用了闲余时间看了一些源码,做个小记录流程图源码分析调用时,会调用的方法,同时将新的作为参数传进会先调用获取一个维护两个时间一个 系列文章 React Fiber源码分析 第一篇 React Fiber源码分析 第二篇(同步模式) React Fiber源码分析 第三篇(...

    worldligang 评论0 收藏0
  • React Fiber源码分析 第二篇(同步模式)

    摘要:函数主要执行两个操作,一个是判断当前是否还有任务,如果没有,则从链中移除。 系列文章 React Fiber源码分析 第一篇 React Fiber源码分析 第二篇(同步模式) React Fiber源码分析 第三篇(异步状态) React Fiber源码分析 第四篇(归纳总结) 前言 React Fiber是React在V16版本中的大更新,利用了闲余时间看了一些源码,做个小记...

    OBKoro1 评论0 收藏0
  • 性能优化

    摘要:如果你的运行缓慢,你可以考虑是否能优化请求,减少对的操作,尽量少的操,或者牺牲其它的来换取性能。在认识描述这些核心元素的过程中,我们也会分享一些当我们构建的时候遵守的一些经验规则,一个应用应该保持健壮和高性能来维持竞争力。 一个开源的前端错误收集工具 frontend-tracker,你值得收藏~ 蒲公英团队最近开发了一款前端错误收集工具,名叫 frontend-tracker ,这款...

    liangzai_cool 评论0 收藏0
  • 剖析 React 源码:render 流程(一)

    摘要:大家可以看到是构造函数构造出来的,并且内部有一个对象,这个对象是本文接下来要重点介绍的对象,接下来我们就来一窥究竟吧。在构造函数内部就进行了一步操作,那就是创建了一个对象,并挂载到了上。下一篇文章还是流程相关的内容。这是我的剖析 React 源码的第二篇文章,如果你没有阅读过之前的文章,请务必先阅读一下 第一篇文章 中提到的一些注意事项,能帮助你更好地阅读源码。 文章相关资料 React ...

    hiYoHoo 评论0 收藏0

发表评论

0条评论

jsdt

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<