• React源码解读之React Fiber


    开始之前,先讲一下该文章能帮你解决哪些问题?

    • facebook为什么要使用重构React
    • React Fiber是什么
    • React Fiber的核心算法 - react是如何中断重启任务的
    • react fiber部分源码简化版

    前言

    该文章涉及的源码部分基于React v17.0.2

    why React Fiber

    • 浏览器渲染过程

    从浏览器的运行机制谈起。大家都知道,浏览器是多进程多线程的,多进程包括主进程,渲染进程,插件进程,GPU进程等,作为前端开发者,我们主要关注其中的渲染进程,这里是页面渲染,HTML解析,css解析,js执行所在的地方。在渲染进程中包括多个线程,此次核心关注页面渲染的两个线程,GUI线程和JS线程。

    GUI线程负责浏览器界面的渲染,包括解析HTML,CSS,布局绘制等;js线程包含我们通常编写的js代码的解析引擎,最有名的就是google的V8。需要注意的一点是,js引擎和GUI渲染是互斥的,因为JS可能会更改HTML或者CSS样式,如果同时执行会导致页面渲染混乱,所以当JS引擎执行时,GUI渲染线程会被挂起,等JS引擎执行完立即执行。

    • GPU渲染

    我们通常看到的动画,视频本质上是通过一张张图片快速的闪过,欺骗人类的双眼,让人以为是连续的动画,每秒内包含的图片越多动画越流畅,正常60张图片可以让人眼感觉是流畅的动画,所以当前大部分设备的FPS是60,即,每秒60帧。所以Chrome要在16ms的时间内执行完下图的渲染任务,才能让用户感觉不到掉帧。

    img

    所以,如果JS执行时间过长,基本上超过10ms之后,用户会感觉到明显的卡顿,很影响用户体验(下文中js执行都以16ms为分界点,不计算后续的渲染,实际的可执行时间肯定小于16ms)。而React执行是要进行两棵树的diff,虽然React根据html的特性对diff算法做了优化,但是如果两棵树比对的层级较深,依旧会远远超过16ms。

    React Fiber

    基于此,那如何解决问题呢?在上图中,React作为js,所有的同步操作执行在最开始,在React执行完成后,后续的html解析,布局渲染等操作才会执行。最容易想到的就是,优化JS的执行速度,把React占用线程的时间缩短到16ms以内。在React执行中,最耗时的就是diff算法,React针对html这种场景下做了优化,业界已经没有更好的算法可以缩短diff算法的时间,所以当树的层次很深时,执行时间依旧很长。

    那还有什么办法呢,我们依旧可以看上图,在现代浏览器中,浏览器为了让开发者知道浏览器执行完当前帧所有的操作后,还有多长时间可以使用,提供了requestIdleCallback这么一个方法,据此我们可以知道当前还有多长时间可以执行。

    requestIdleCallback
    requestIdleCallback((deadline) => {
       
        while ((deadline.timeRemaining() > 0 || deadline.didTimeout) && nextComponent) {
       
            nextComponent = performWork(nextComponent);
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    题外话:

    有兴趣可以在控制台执行输出一下requestIdleCallback回调参数的requestIdleCallback((deadline) ,在不同的网页上得到的时间可能不同。甚至可能会超过16ms(在React官网就显示49.9ms)因为requestIdleCallback的一些限制原因,React源码中未使用requestIdleCallback,而是自己实现了一套类似的机制。

    使用此方法我们知道每帧的剩余时间之后,这样就可以在剩余时间内进行工作,如果当前帧时间不够,就把剩余的工作放到下一帧的requestIdleCallback中执行。这就是React所说的时间切片(time slicing)。

    所以要使用此方法,需要把基于js内置栈调用的同步递归遍历的diff算法改为异步增量更新。按照React负责人的说法就是

    如果你仅仅依赖js内置的调用栈,它会一直执行直到栈为空…,如果我们可以任意的中断并且手动的操作调用栈,不是更完美吗?这就是React Fiber的目的。Fiber是针对React Component的栈的重新实现。你可以认为一个Fiber就是一个虚拟的栈中的一项任务。

    说人话,就是原来树的递归是深度递归遍历,现在需要把递归算法重新实现,以便于我不依赖于栈的调用,可以对react组件一个一个节点的遍历,中途任意时间可以中断和从当前开始。相关参考视频讲解:进入学习

    stack Reconciliation vs Fiber Reconciliation

    stack Reconciliation

    假如我们有如下一个html结构

    image-47.png

    转化成类React组件的js对象如下

    const a1 = {
       name: 'a1'};
    const b1 = {
       name: 'b1'};
    const b2 = {
       name: 'b2'};
    const b3 = {
       name: 'b3'};
    const c1 = {
       name: 'c1'};
    const c2 = {
       name: 'c2'};
    const d1 = {
       name: 'd1'};
    const d2 = {
       name: 'd2'};
    
    a1.render = () => [b1, b2, b3];
    b1.render = () => [];
    b2.render = () => [c1];
    b3.render = () => [c2];
    c1.render = () => [d1, d2];
    c2.
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
  • 相关阅读:
    SQL6 查找学校是北大的学生信息
    .NET周刊【5月第2期 2024-05-12】
    Milk Scheduling S——拓扑排序
    django rest framework 学习笔记-实战商城3
    C++中将 sizeof() 用于类
    16,8和4位浮点数是如何工作的
    [LeetCode 779]第K个语法符号
    1. 爬虫之Beautifulsoup解析库&在线解析图片验证码
    Linux操作系统:IPTables
    数据库技术基础
  • 原文地址:https://blog.csdn.net/weixin_59558923/article/details/127877617