react fiber执行原理示例解析

目录
  • 为什么要使用fiber,要解决什么问题?
  • fiber是什么?
    • 数据结构
    • 执行单元
    • 浏览器工作:
  • Fiber执行原理
    • workInProgress tree:
    • currentFiber tree:
    • Effects list:
    • render阶段:
      • 遍历节点过程:
      • 收集effect list:
    • commit阶段:
      • 为什么commit必须是同步的操作的?

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

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

所以我们要解决的问题就是:解决React主线程长时间占用的一个问题。 这个时候,就引入了Fiber架构。

fiber是什么?

Fiber 可以理解为是一个执行单元,也可以理解为是一种数据结构。每一个React元素都对应一个fiber对象,我们先看看fiber中的属性:

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // 作为静态数据结构的属性
  this.tag = tag;	     // Fiber对应组件的类型 Function/Class/Host...
  this.key = key;	     // key属性
  this.elementType = null;   // 大部分情况同type,某些情况不同,比如FunctionComponent使用React.memo包裹
  this.type = null;					// 对于 FunctionComponent,指函数本身,对于ClassComponent,指class,对于HostComponent,指DOM节点tagName
  this.stateNode = null;		// Fiber对应的真实DOM节点
  // 用于连接其他Fiber节点形成Fiber树
  this.parent = null;		// 指向父级Fiber节点
  this.child = null;		// 指向子Fiber节点
  this.sibling = null;	// 指向右边第一个兄弟Fiber节点
  this.index = 0;
  this.ref = null;
  // 作为动态的工作单元的属性 —— 保存本次更新造成的状态改变相关信息
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;		// class 组件 Fiber 节点上的多个 Update 会组成链表并被包含在 fiber.updateQueue 中。 函数组件则是存储 useEffect 的 effect 的环状链表。
  this.memoizedState = null;	// hook 组成单向链表挂载的位置
  this.dependencies = null;
  this.mode = mode;
  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;
  // 调度优先级相关
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  // 指向该fiber在另一次更新时对应的fiber
  this.alternate = null;
}

数据结构

React Fiber 就是采用链表实现的,主要就是通过以下这几个属性表示:

  this.parent = null;		// 指向父级Fiber节点
  this.child = null;		// 指向子Fiber节点
  this.sibling = null;	// 指向右边第一个兄弟Fiber节点

假如我们要渲染下面这个元素树:

<div>
    <h1>
        <p>
            <a></a>
        </p>
    </h1>
    <h2></h2>
</div>

我们看一下它的Fiber结构树:

每个fiber元素都有这三个属性,观察上面图发现:

  • parent:指向父级Fiber节点:
  • child:指向子Fiber节点
  • sibling:指向右边的兄弟节点

执行单元

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

浏览器工作:

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

  • 首先需要处理输入事件,能够让用户得到最早的反馈
  • 接下来是处理定时器,需要检查定时器是否到时间,并执行对应的回调
  • 接下来处理 Begin Frame(开始帧),即每一帧的事件,包括 window.resizescrollmedia query change
  • 接下来执行请求动画帧 requestAnimationFrame(rAF),即在每次绘制之前,会执行 rAF 回调
  • 紧接着进行 Layout 操作,包括计算布局和更新布局,即这个元素的样式是怎样的,它应该在页面如何展示
  • 接着进行 Paint 操作,得到树中每个节点的尺寸与位置等信息,浏览器针对每个元素进行内容填充
  • 到这时以上的六个阶段都已经完成了,接下来处于空闲阶段(Idle Peroid),可以在这时执行 requestIdleCallback 里注册的任务

这样我们把工作单元的任务放到requestIdleCallback回调当中,如果浏览器处理完上述的任务(布局和绘制之后),还有盈余时间,这个时候就可以执行我们的工作单元了。每次执行完一个执行单元。React会去检测还剩多少时间,如果没有时间就将控制权让给浏览器。直至,React和浏览器通过合作式调度完美配合,实现高性能应用。

Fiber执行原理

从根节点开始调度和渲染可以分为两个阶段:rendercommit。 先来了解下这几个关键名词:

workInProgress tree:

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

currentFiber tree:

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

Effects list:

effect list 可以理解为是一个存储 effect 副作用列表容器。

render阶段:

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

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

遍历节点过程:

遍历Fiber tree时采用的是后序遍历方法

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

收集effect list:

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

  • firstEffect:指向第一个有副作用的子fiber
  • lastEffect:指向最后一个有副作用的子fiber

中间的使用nextEffect做成一个单链表。

commit阶段:

render阶段不同,commit阶段是同步操作的。

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

因为在commit阶段是更新真实的dom,所以更新dom不可能一点一点去更新,这样用户体验会极差。所以commit阶段必须是同步执行,一次更新到位。

首先的事情是遍历effect-list列表,拿到每一个 effect 存储的信息,根据副作用类型 effectTag 执行相应的处理并提交更新到真正的 DOM。所有的effects都会在layout phase阶段之前被处理。当该阶段执行结束时,workInProgress树会被替换成current树。到这里,根据收集到的变更信息完成了刷新操作。

以上就是react fiber执行原理示例解析的详细内容,更多关于react fiber执行原理的资料请关注我们其它相关文章!

(0)

相关推荐

  • 详解React Fiber的工作原理

    啥是React Fiber? React Fiber,简单来说就是一个从React v16开始引入的新协调引擎,用来实现Virtual DOM的增量渲染. 说人话:就是一种能让React视图更新过程变得更加流畅顺滑的处理手法. 我们都知道:进程大,线程小.而Fiber(纤维)是一种比线程还要细粒度的处理机制.从这个单词也可以猜测:React Fiber会很"细".到底怎么个细法,我们接着往下看. 为什么会有React Fiber? 之前说了,React Fiber是为了让React的视

  • React Fiber 链表操作及原理示例详解

    目录 正文 什么是Fiber Fiber节点React源码 Fiber树是链表 节点独立 节省操作时间与单向操作 利于双缓存与异步可中断更新操作 异步可中断更新 双缓存 正文 看了React源码之后相信大家都会对Fiber有自己不同的见解,而我对Fiber最大的见解就是这玩意儿就是个链表.如果把整个Fiber树当成一个整体确实有点难理解源码,但是如果把它拆开了,将每个节点都看成一个独立单元却能得到一个很清晰的思路,接下来我就简单几点讲讲,我所认为的为什么React要用链表这种数据结构来构建Fib

  • React Fiber结构的创建步骤

    React Fiber的创建 当前React版本基于V17.0.2版本,本篇主要介绍fiber结构的创建. 一.开始之前 个人理解,如有不对,请指出. 首先需要配置好React的debugger开发环境,入口在这里:github 执行npm run i,安装依赖,npm start运行环境. 二.从React.render开始 通过在项目入口处调用React.render,打上Debug,查看React调用栈. const root = document.getElementById('root

  • 详解React Fiber架构原理

    目录 一.概述 二.Fiber架构 2.1 执行单元 2.2 数据结构 2.3 Fiber链表结构 2.4 Fiber节点 2.5 API 2.5.1 requestAnimationFrame 2.5.2 requestIdleCallback 三.Fiber执行流程 3.1 render阶段 3.1.1 遍历流程 3.1.2 收集effect list 3.2 commit阶段 3.2.1 根据effect list 更新视图 3.2.2 视图更新 四.总结 一.概述 在 React 16

  • react-three-fiber实现炫酷3D粒子效果首页

    目录 背景 Three.js工作原理 场景(Scene) 相机(Camera) 渲染器(renderer) 网格对象(mesh) react-three-fiber 画布(Canvas) 3D粒子模型构建 实现思路 定义buffer几何体,并填充数据 将buffer几何体包裹在点模型中,并为每个点引入材质 加入旋转动画 最后呈现结果 背景 初学者怎么用react-three-fiber实现一个炫酷粒子效果的首页 Three.js工作原理 场景(Scene).相机(Camera)和渲染器(Rend

  • react fiber执行原理示例解析

    目录 为什么要使用fiber,要解决什么问题? fiber是什么? 数据结构 执行单元 浏览器工作: Fiber执行原理 workInProgress tree: currentFiber tree: Effects list: render阶段: 遍历节点过程: 收集effect list: commit阶段: 为什么commit必须是同步的操作的? 为什么要使用fiber,要解决什么问题? 在 react16 引入 Fiber 架构之前,react 会采用递归方法对比两颗虚拟DOM树,找出需

  • React Streaming SSR原理示例深入解析

    目录 功能简介 基本原理 使用示例 Streaming HTML Selective Hydration 降级逻辑 JS 和 CSS 设置 源码解析 数据结构 Segment Boundary Task Request 主要流程 功能简介 React 18 提供了一种新的 SSR 渲染模式: Streaming SSR.通过 Streaming SSR,我们可以实现以下两个功能: Streaming HTML:服务端可以分段传输 HTML 到浏览器,而不是像 React 18 以前一样,需要等待

  • Kotlin协程之Flow基础原理示例解析

    目录 引言 一.Flow的创建 二.Flow的消费 1.SafeFlow类 2.AbstractFlow类 3. SafeCollector类 4.消费过程中的挂起 引言 本文分析示例代码如下: launch(Dispatchers.Main) { flow { emit(1) emit(2) }.collect { delay(1000) withContext(Dispatchers.IO) { Log.d("liduo", "$it") } Log.d(&qu

  • go语言csrf库使用实现原理示例解析

    目录 引言 csrf小档案 一.CSRF及其实现原理 CSRF攻击示例 二.如何预防 三.CSRF包的使用及实现原理 csrf包的安装 基本使用 使用net/http包启动的服务 echo框架下使用csrf包 gin框架下使用csrf包 beego框架下使用csrf包 实现原理 csrf结构体 csrf包的工作流程 为什么GET.HEAD.OPTIONS.TRACE的请求方法不需要token验证 总结 引言 今天给大家推荐的是web应用安全防护方面的一个包:csrf.该包为Go web应用中常见

  • useReducer createContext代替Redux原理示例解析

    目录 前言 采用react-redux实现 采用react hooks模拟redux实现 异步action 总结 前言 最近看到很多采用useReducer + createContext 实现一个简易的redux的方案,今天亲自试了一下,发现还是会有一些区别的. 采用react-redux实现 这里使用react-redux 实现一个简单的状态管理例子. App.jsx根组件 import React from 'react'; import { Button } from './Button

  • java LockSupport实现原理示例解析

    目录 引言 LockSupport常见函数 LockSupport.park LockSupport.unpark 引言 前文中了解到AQS借助LockSupport.park和LockSupport.unpark完成线程的阻塞和唤醒,那么LockSupport内部又是怎么实现的?这是一个什么类? LockSupport是用于使用锁阻塞线程的基础实现,是其他同步类的基础,这个类为每个使用它的线程关联一个许可证(有点类似于Semaphore),如果许可证可用,线程调用park方法时会立即返回,线程

  • go sync Once实现原理示例解析

    目录 正文 Once 的实现 使用示例 Once 的一些工作机制 Once 详解 hotpath atomic.LoadUint32 atomic.StoreUint32 Mutex 总结 正文 在很多情况下,我们可能需要控制某一段代码只执行一次,比如做某些初始化操作,如初始化数据库连接等. 对于这种场景,go 为我们提供了 sync.Once 对象,它保证了某个动作只被执行一次. 当然我们也是可以自己通过 Mutex 实现 sync.Once 的功能,但是相比来说繁琐了那么一点, 因为我们不仅

  • 网页资源阻塞浏览器加载的原理示例解析

    目录 正文 测试前环境准备 图片会造成阻塞吗? CSS 加载阻塞 CSS 会阻塞后面 JS 的执行吗? JS 加载阻塞 defer 和 async 动态脚本会造成阻塞吗? DOMContentLoaded 和 onload DOMContentLoaded 遇到脚本 DOMContentLoaded 遇到样式 正文 一个页面允许加载的外部资源有很多,常见的有脚本.样式.字体.图片和视频等,对于这些外部资源究竟是如何影响整个页面的加载和渲染的呢?今天来一探究竟. 如何用 Chrome 定制网络加载

  • OPENMP SECTIONS CONSTRUCT原理示例解析

    目录 前言 编译器角度分析 动态库函数分析 总结 前言 在本篇文章当中主要给大家介绍 OpenMP 当中主要给大家介绍 OpenMP 当中 sections construct 的实现原理以及他调用的动态库函数分析.如果已经了解过了前面的关于 for 的调度方式的分析,本篇文章就非常简单了. 编译器角度分析 在这一小节当中我们将从编译器角度去分析编译器会怎么处理 sections construct ,我们以下面的 sections construct 为例子,看看编译器是如何处理 sectio

随机推荐