我要拿捏 react 系列二: React 架构设计

话说这么快进阶到 React架构设计,我是有点抵触的,但是书就是这样的顺序,那就一起看看吧

1. 虚拟 DOM 与 fiber

Virtual DOM 不是真实的 DOM,但是却以对象的形式描述着 DOM 的特征

为什么要有虚拟 DOM?

一方面,浏览器向 JS 提供了各种各样的接口,去操控这些 DOM。 比如 createElement、 appendChild 等。 但是浏览器频繁调用这些 API 的同时,做了计算、布局、绘制、栅格化、合成等操作, 这些都是浪费浏览器性能的

JS 的执行速度要远优于浏览器渲染速度,如果先操作 Virtual DOM,然后统一调用 DOM API,一定程度上可以减少浏览器的性能消耗。

另一方面,有了 Virtual DOM,前端开发者无须过多关心怎样处理 DOM 元素等问题,而是直接根 据 Data 数据改变虚拟 DOM 状态,再由 Virtual DOM 映射成真实的 DOM 元素,这就让数据驱动视图变 得更加顺畅。

**fiber 就是 React 的 Virtual DOM。 **

为什么要用 fiber,fiber 解决了哪些问题?

在 React V15 以及之前的版本,React 对于 Virtual DOM 是采用递归方式遍历更新的,比如 一次更新,就会从应用根部递归更新,递归一旦开始,中途无法中断,随着项目越来越复杂,层级越 来越深,导致更新的时间越来越长,给前端交互上的体验就是卡顿。

React V16 为了解决卡顿问题,引入了 fiber,为什么它能解决卡顿,

每一个 fiber 都可以作为一个执行单元来处理,可以把主动权交给浏览器,等浏览器有空余时间,通过 scheduler (调度器)再次恢复渲染

Element、fiber、真实 DOM 三者到底是什么关系?

Element 是 React 视图层在代码层级上的表象,也就是开发者写的 JSX 语法、写的元素结构,都 会被创建成 Element 对象的形式。其中保存了 props、Children 等信息。

DOM 是元素在浏览器上给用户直观的表象。

fiber 可以说是 Element 和真实 DOM 之间的交流枢纽站,一方面每一个类型 Element,都会有一个 与之对应的 fiber 类型,Element 变化引起更新流程,都是通过 fiber 层面做一次调和改变,然后对于元 素,形成新的 DOM 做视图渲染。

2. fiber 架构

fiber 树的如何建立连接的?

每个 DOM 元素并非独立的,DOM 元素之间通过 Children 等属性构建起来。

每个 fiber 也并非是独立的个体,每个 fiber 之间是需要建立起关联的

js 复制代码
function FiberNode(tag,pendingProps,key,mode){
    this.return = null;  // 指向父级 fiber 节点。
    this.child = null;  // 指向子 fiber 节点。 
    this.sibling = null;  // 指向兄弟 fiber 节点。
}

所有的 fiber 就是通过这三个属性建立起关联的。

fiber 树的创建流程

1. 双缓冲树

canvas 绘制动画的时候,如果上一帧计算量比较大,导致清除(重点在清除)上一帧画面到绘制当前帧画面之间 有较长的间隙,就会出现白屏。

为了解决这个问题,canvas 在内存中绘制当前动画,绘制完毕后,直接用当前帧替换上一帧画面,由于省去了两帧替换间的计算时间,不会出现从白屏到画面的闪烁情 况。这种在内存中构建并直接替换的技术叫作双缓存。

React 用 workInProgress 树 (内存中构建的树)和 Current (渲染树)来实现更新逻辑。

双缓存一个在内存中构建,一个渲染视图,两棵树用 alternate 指针相互指向,

重点!!:在下一次渲染时,直接复用缓存 树作为下一次渲染树,上一次的渲染树又作为缓存树, 这样可以防止只用一棵树更新状态丢失的情 况,又加快了 DOM 节点的替换与更新。

workInProgress 树:正在内存中构建的 fiber 树称为 workInProgress fiber 树。 在一次更新中,所有的 更新发生在 workInProgress 树上。 在一次更新之后,workInProgress 树上的状态是最新的, 它将变成 Current 树,用于渲染视图。

Current 树:正在视图层渲染的树叫作 Current 树。

这里有一个细节,就是在内存中构建的树叫作 workInProgress 树,因为树上的节点是一个接着一个创建更新的,所以正在构建的 fiberNode 就叫作 workInProgress(翻译:正在创建的,正在施工的)。当创建完 Index 组件对应的 fiberNode 之后,这个 fiberNode 就作为了新的 workInProgress, workInProgress(正在施工创建的,所以永远指向正在工作更新的fiberNode) 就像一个一直变动的指针,永远指 向正在工作更新的 fiberNode。

2.Element 是如何变成 fiber 的?

首先会解析 props 上面 的属性,比如解析 Element 元素的类型变成 fiber 的类型 tag,解析 Element 上的 props 变成 pendingProps,最后通过 New 一个 fiberNode 形成一个 fiber 对象

接下来把新创建的 fiber 的 return 指针指向父级 fiber。 至此完成了子代 fiber 树的构建流程。

3. sibling指针的形成

多个子节点构建 fiber 的流程,大致就是先遍历子代 Element 对象,然后逐一创建 fiber,每个子代 fiber 的 return 指针都指向父级 fiber,每个子代 fiber 通过 sibling 建立起关系。

最后会以形成的整个 workInProgress 树作为最新的渲染树。fiberRoot 的 Current 指针指向 workIn- Progress 树,使其变为 Current fiber 树。到此完成初始化流程。

fiber 树的更新流程

组件更新,接下来还会通过 renderWithHooks 执行函数组件,得到新的 Children,

然后会执行 update 更新流 程,大体和初始化流程类似,有区别的是现在已经存在了 Current 树,所以会复用 Current 树上的一些 状态,重新形成 workInProgress 树,

整个 workInProgress 树构建之后,fiberRoot 的 Current 指针指向 workInProgress 树,使其变为 Current fiber 树,完成更新流程

3. React 中的位运算

为什么要用位运算

在一个场景下,会有很多状态常量 A、B、C,这些状态在整个应用中的一些关键节点中做流程

js 复制代码
if(value = = = A){ // TODO...

}

此时 value 属性是简单的一对一关系,但是实际场景下,value 可能是好几个枚举常量的集合,也就是一对多的关系,

如何用一个 value 表示 A 和 B 两个属性的集合? 这个时候位运算就派上用场了,因为可以把一些状态常量用 32 位的二进制来表示

这样如果一个值既代表 A 又代表 B,那么就可以通过位运算的 | 来处理。就有: AB = A | B = 0b0000000000000000000000000000011

React 应用中有很多位运算的场景

更新优先级场景

在 React V17 及以上的版本中,引入了一个新的属性,用来代表更新任务的优先级,它就是 Lane,Lane 代表的数值越小,此 次更新的优先级就越大

一个 Lane 可能 需要表现出多个更新优先级。所以通过位运算,让多个优先级的任务合并

4.React 数据更新架构设计

React 更新前置设计

1. 批量更新:减少更新次数

(1)减少更新次数,从而减少浏览器的渲染绘制操作,比如重绘、回流等。 (2)避免 JS 的执行,影响到浏览器的绘制。

2. 更新调度:更新由浏览器控制
3. 更新标识 Lane 和 ExpirationTime
4. 进入更新

会把双 缓冲树交替使用作为最新的渲染 fiber 树。 在构建最新 fiber 树的时候,没有发生更新的地方是不需要 处理的,直接跳过更新即可,这也是一种性能上的优化

React 更新后置设计

React 在进入到更新流程之后,并不是马上更新数据,更新 DOM 元素,而是通过渲染和 commit两大阶段来处理整个流程。

在渲染阶段中,核心思想就是 diff 对比,会给 fiber 打上不同的 flag 标志,

经历了渲染阶段之后,就进入到 commit 阶段,commit 阶段会执行更新,然后就会执行一些生命 周期和更新回调函数,所以 React 开发者就可以拿到更新后的 DOM 元素。

5.React 事件系统设计

React 事件系统介绍

对于不同的浏览器,对事件存在不同的兼容性,React 想实现一个兼容全浏览器的框架,为了实现这个目标,就需要创建一个兼容全浏览器的事件系统,以此抹平不同浏览器的差异。

所以 React 也开发了一套自己的事件系统。

  • (1)给元素绑定的事件,不是真正的事件处理函数。
  • (2)甚至在事件处理函数中拿到的事件源 e,也不是真正的事件源 e。

事件系统设计

  1. 事件可控性
  2. 跨平台兼容
  3. 事件合成机制

在原生 DOM 中是没有 onChange 事件的,对于 onChange 事件,原生事件中会有多个事件与之对应。onChange 由 blur、change、focus 等多个事件合成。

  1. 事件统一处理函数:事件委托
  2. 冒泡和捕获的处理

addEventListener(type, listener, useCapture)

言归正传,在绑定事件监听器的时候,绑定两次就可以了,也就是在冒泡和捕获阶段各绑定,这样 onClick 事件就可以在冒泡阶段执行,onClickCapture 事件也可以在捕获阶段执行了。

总结一下:我觉这章只是一个架构设计的要点概述,我们就先对 react架构设计有个大致的印象和了解即可,下期再见!

相关推荐
java_heartLake12 分钟前
Vue3之性能优化
javascript·vue.js·性能优化
Swift社区15 分钟前
HarmonyOS 实践 - 设计模式在代码中的作用
javascript
哑巴语天雨36 分钟前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情1 小时前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
乔峰不是张无忌3301 小时前
【HTML】动态闪烁圣诞树+雪花+音效
前端·javascript·html·圣诞树
码农老起1 小时前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
前端没钱2 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
汪洪墩2 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
我曾经是个程序员2 小时前
鸿蒙学习记录
开发语言·前端·javascript