翼度科技»论坛 编程开发 JavaScript 查看内容

react fiber执行原理示例解析

8

主题

8

帖子

24

积分

新手上路

Rank: 1

积分
24
为什么要使用fiber,要解决什么问题?

  1. react16
复制代码
引入
  1. Fiber
复制代码
架构之前,react 会采用递归方法对比两颗虚拟DOM树,找出需要改动的节点,然后同步更新它们,这个过程
  1. react
复制代码
称为reconcilation(协调)。在
  1. reconcilation
复制代码
期间,
  1. react
复制代码
会同步执行操作,提交到真实 DOM 的更改,会一直占着浏览器的资源,不能中断,中断后就不能恢复,使得我们一些用户操作定时器等等事件无法得到响应,是一个非常糟糕的用户体验。
所以我们要解决的问题就是:解决React主线程长时间占用的一个问题。 这个时候,就引入了
  1. Fiber
复制代码
架构。

fiber是什么?

  1. Fiber
复制代码
可以理解为是一个执行单元,也可以理解为是一种数据结构。每一个React元素都对应一个fiber对象,我们先看看
  1. fiber
复制代码
中的属性:
  1. function FiberNode(
  2.   tag: WorkTag,
  3.   pendingProps: mixed,
  4.   key: null | string,
  5.   mode: TypeOfMode,
  6. ) {
  7.   // 作为静态数据结构的属性
  8.   this.tag = tag;             // Fiber对应组件的类型 Function/Class/Host...
  9.   this.key = key;             // key属性
  10.   this.elementType = null;   // 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹
  11.   this.type = null;                                        // 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName
  12.   this.stateNode = null;                // Fiber对应的真实DOM节点
  13.   // 用于连接其他Fiber节点形成Fiber树
  14.   this.parent = null;                // 指向父级Fiber节点
  15.   this.child = null;                // 指向子Fiber节点
  16.   this.sibling = null;        // 指向右边第一个兄弟Fiber节点
  17.   this.index = 0;
  18.   this.ref = null;
  19.   // 作为动态的工作单元的属性 —— 保存本次更新造成的状态改变相关信息
  20.   this.pendingProps = pendingProps;
  21.   this.memoizedProps = null;
  22.   this.updateQueue = null;                // class 组件 Fiber 节点上的多个 Update 会组成链表并被包含在 fiber.updateQueue 中。 函数组件则是存储 useEffect 的 effect 的环状链表。
  23.   this.memoizedState = null;        // hook 组成单向链表挂载的位置
  24.   this.dependencies = null;
  25.   this.mode = mode;
  26.   // Effects
  27.   this.flags = NoFlags;
  28.   this.subtreeFlags = NoFlags;
  29.   this.deletions = null;
  30.   // 调度优先级相关
  31.   this.lanes = NoLanes;
  32.   this.childLanes = NoLanes;
  33.   // 指向该fiber在另一次更新时对应的fiber
  34.   this.alternate = null;
  35. }
复制代码

数据结构

React Fiber 就是采用链表实现的,主要就是通过以下这几个属性表示:
  1.   this.parent = null;                // 指向父级Fiber节点
  2.   this.child = null;                // 指向子Fiber节点
  3.   this.sibling = null;        // 指向右边第一个兄弟Fiber节点
复制代码
假如我们要渲染下面这个元素树:
  1. <div>
  2.     <h1>
  3.         <p>
  4.             <a></a>
  5.         </p>
  6.     </h1>
  7.     <h2></h2>
  8. </div>
复制代码
我们看一下它的Fiber结构树:

每个
  1. fiber
复制代码
元素都有这三个属性,观察上面图发现:


    1. parent
    复制代码
    :指向父级
    1. Fiber
    复制代码
    节点:
    1. child
    复制代码
    :指向子
    1. Fiber
    复制代码
    节点
    1. sibling
    复制代码
    :指向右边的兄弟节点

执行单元

我们可以把每个
  1. fiber
复制代码
当做一个执行单元,每次执行完一个执行单元。
  1. React
复制代码
会去检测还剩多少时间,如果没有时间就将控制权让给浏览器,如果还有时间就去执行下一个执行单元。
这里就涉及到了一个问题,
  1. react
复制代码
如何和浏览器进行控制权的交接,浏览器何时空闲呢?。我们先来了解一下浏览器的工作:

浏览器工作:

在浏览器中,我们所看到的页面是一帧一帧画出来的,渲染的帧率与设备的刷新率保持一致。通常情况下,我们的设备都是
  1. 60Hz
复制代码
,也就是说,
  1. 1s
复制代码
屏幕会刷新
  1. 60
复制代码
次。当每秒内绘制的帧数(
  1. FPS
复制代码
)超过
  1. 60
复制代码
时,页面渲染是流畅的,当帧数小于
  1. 60
复制代码
时,会明显感受到卡顿。下面来看完整的一帧中,浏览器具体做了哪些事情:



  • 首先需要处理输入事件,能够让用户得到最早的反馈
  • 接下来是处理定时器,需要检查定时器是否到时间,并执行对应的回调
  • 接下来处理
    1. Begin Frame
    复制代码
    (开始帧),即每一帧的事件,包括
    1. window.resize
    复制代码
    1. scroll
    复制代码
    1. media query change
    复制代码

  • 接下来执行请求动画帧
    1. requestAnimationFrame(rAF
    复制代码
    ),即在每次绘制之前,会执行
    1. rAF
    复制代码
    回调
  • 紧接着进行
    1. Layout
    复制代码
    操作,包括计算布局和更新布局,即这个元素的样式是怎样的,它应该在页面如何展示
  • 接着进行
    1. Paint
    复制代码
    操作,得到树中每个节点的尺寸与位置等信息,浏览器针对每个元素进行内容填充
  • 到这时以上的六个阶段都已经完成了,接下来处于空闲阶段(
    1. Idle Peroid
    复制代码
    ),可以在这时执行
    1. requestIdleCallback
    复制代码
    里注册的任务
这样我们把工作单元的任务放到
  1. requestIdleCallback
复制代码
回调当中,如果浏览器处理完上述的任务(布局和绘制之后),还有盈余时间,这个时候就可以执行我们的工作单元了。每次执行完一个执行单元。
  1. React
复制代码
会去检测还剩多少时间,如果没有时间就将控制权让给浏览器。直至,
  1. React
复制代码
和浏览器通过合作式调度完美配合,实现高性能应用。

Fiber执行原理

从根节点开始调度和渲染可以分为两个阶段:
  1. render
复制代码
  1. commit
复制代码
。 先来了解下这几个关键名词:

workInProgress tree:

  1. workInProgress
复制代码
代表当前正在执行更新的
  1. Fiber
复制代码
树。在
  1. setState
复制代码
或者渲染 后,会构建一颗
  1. Fiber
复制代码
树,也就是
  1. workInProgress tree
复制代码


currentFiber tree:

首次渲染之后,
  1. React
复制代码
会生成一个对应于
  1. UI
复制代码
渲染的
  1. fiber
复制代码
树,称之为 current 树。在新一轮更新时
  1. workInProgress tree
复制代码
再重新构建,新
  1. workInProgress
复制代码
的节点通过
  1. alternate
复制代码
属性和
  1. currentFiber
复制代码
的节点建立联系。

Effects list:

  1. effect list
复制代码
可以理解为是一个存储
  1. effect
复制代码
副作用列表容器。

render阶段:

  1. render
复制代码
阶段中,会找到所有节点的变更,比如说节点新增,编辑,删除等等。这些变更
  1. React
复制代码
称之为副作用effect。在这个阶段中,也可以认为是
  1. diff
复制代码
阶段,主要就是对比
  1. currentFiber tree
复制代码
  1. workInProgress tree
复制代码
之间的差异,然后打上
  1. 标记
复制代码

在这个阶段,任务是可以终止的。React 可以根据当前可用的时间片处理一个或多个
  1. fiber
复制代码
节点,并且得益于
  1. fiber
复制代码
对象中存储的元素上下文信息以及构成的链表结构,使其能够将执行到一半的工作仍保存在内存的链表中。在重新获得控制权后,又可以根据保存在内存中的上下文信息快速找到停止的
  1. fiber
复制代码
节点,然后继续工作执行工作单元。

遍历节点过程:

遍历
  1. Fiber tree
复制代码
时采用的是后序遍历方法


  • 从顶部开始遍历
  • 如果有child节点,且还未遍历,遍历child节点
  • 如果有child节点,且已经遍历过,则遍历sibling节点。
  • 如果没有child节点,返回父节点
  • 如果最后返回的节点为顶部,表示所有节点遍历完成。


收集effect list:

在遍历的过程中,我们会去收集所有变更的节点产出的
  1. effect
复制代码
,每个
  1. effect
复制代码
通过链表的方式链接。每个 fiber 有两个属性


  • firstEffect:指向第一个有副作用的子fiber
  • lastEffect:指向最后一个有副作用的子fiber
中间的使用
  1. nextEffect
复制代码
做成一个单链表。

commit阶段:

  1. render
复制代码
阶段不同,
  1. commit
复制代码
阶段是同步操作的。

为什么commit必须是同步的操作的?

因为在
  1. commit
复制代码
阶段是更新真实的
  1. dom
复制代码
,所以更新dom不可能一点一点去更新,这样用户体验会极差。所以
  1. commit
复制代码
阶段必须是同步执行,一次更新到位。
首先的事情是遍历
  1. effect-list
复制代码
列表,拿到每一个
  1. effect
复制代码
存储的信息,根据副作用类型
  1. effectTag
复制代码
执行相应的处理并提交更新到真正的
  1. DOM
复制代码
。所有的
  1. effects
复制代码
都会在
  1. layout phase
复制代码
阶段之前被处理。当该阶段执行结束时,
  1. workInProgress树
复制代码
会被替换成
  1. current树
复制代码
。到这里,根据收集到的变更信息完成了刷新操作。
以上就是react fiber执行原理示例解析的详细内容,更多关于react fiber执行原理的资料请关注脚本之家其它相关文章!

来源:https://www.jb51.net/article/266670.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具