《十五》React 的底层原理

本文介绍了React.StrictMode在开发阶段如何触发额外的检查,包括不安全生命周期、过时API、废弃方法和意外副作用。通过实例展示了如何在App和Home组件中使用它,并强调了其对生产构建的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React 的渲染机制:

请添加图片描述

  1. render() 方法中会返回 JSX,JSX 会被转换成 React.createElement() 函数的调用。
  2. React.createElement() 会创建出来一个 ReactElement(React 元素),ReactElement 是一个虚拟节点,本质上就是一个 JS 对象。
  3. 一堆的 ReactElement 组成一个 JS 的对象树,也就是虚拟 Dom。
  4. 最终 React 根据虚拟 Dom 渲染出真实 Dom (document.createElement()),呈现在页面中。
// JSX
const element = (
	<div className='header'>
		<div>标题</div>
	</div>
)	

// JSX 本质上是 React.createElement()
const element = React.createElement(
	"div", 
	{
	  className: "header"
	},
	React.createElement("div", null, "标题")
)
// 虚拟 DOM 节点
console.log(element)

请添加图片描述

// 真实 DOM 节点
<div class='header'>
	<div>标题</div>
</div>
const dom = document.getElementsByClassName('header')[0]
console.log(dom)

请添加图片描述

React 的更新机制:

请添加图片描述

React 中的虚拟 Dom:

虚拟节点:

虚拟节点:React 会将 JSX 转换成 React.createElement() 函数调用;React.createElement() 会创建出来一个 ReactElement,React 元素就是一个虚拟节点,本质上就是一个 JS 对象。

虚拟 Dom:

虚拟 Dom:ReactElement 组成的对象树,其实就是由一堆 JS 对象来模拟真实的 Dom 结构。

虚拟 Dom 的作用:
  1. 通过虚拟 Dom 和 Diff 算法可以实现视图的高效更新:
    • 在 React 中,所有的 Dom 构造都是通过虚拟 Dom 进行,而不总是直接操作页面真实 Dom,虚拟 Dom 是内存数据,性能极高。
    • 每当数据变化时,React 都会重新构建整个完整的 Dom 树,然后将当前 Dom 树和上一次的 Dom 树进行对比,得到 Dom 结构的区别;然后仅仅将需要变化的部分进行实际的浏览器 Dom更新,而且 React 能够批量处理虚拟 Dom 的刷新。
  2. 通过虚拟 Dom 可以实现跨平台:虚拟 Dom 本质上就是一堆 JS 对象,因此 React 既可以将它渲染成 Web 端的 HTML 元素,也可以通过桥接的方式将它渲染成移动端(IOS、Android)的控件。

React 中的 Diff 算法:

Diff 算法:通过对比新旧 DOM 树,找出其中的差异,最小化更新视图。

传统的 Diff 算法:

在这里插入图片描述
在这里插入图片描述
传统的 Diff 算法需要循环对比两棵树,单纯比较次数就是 O(n^2),找到差异后再重新进行排序,最终时间复杂度可达到 O(n^3)

React 的 Diff 算法:

React 中使用三个层级的策略对传统的 Diff 算法进行了优化,使复杂度从 O(n^3) 降到了 O(n)

React 的 Diff 算法采用了深度优先遍历算法。

tree diff:

树层级的比较:两棵树只对同层级的节点进行比较,不考虑节点的跨层级比较。如果同层级相同位置节点不一样,则直接删除旧的创建新的。

只有创建、删除操作、没有移动操作。

在这里插入图片描述
React 只会对相同颜色框内的节点进行比较,如此只需要遍历一次虚拟 Dom 树,就可以完成整个的对比。
在这里插入图片描述
B 节点发生了跨层级的移动操作,React 并不会复用 B 节点及其子节点,而是会直接删除 A 节点下的 B 节点及其子节点,然后再在 C 节点下创建新的 B 节点及其子节点。

component diff:

组件的比较:如果是同类型的组件(即两个组件是同一个类的两个实例),按照 tree diff 树比对策略进行比较;如果不是同类型的组件,则直接删除旧的创建新的。

只有创建、删除操作、没有移动操作。
对于同类型组件, React 通过让开发人员自定义 shouldComponentUpdate() 方法来进行比较优化,减少组件不必要的比较。如果没有自定义,shouldComponentUpdate() 方法默认返回 true,每次组件发生数据(state & props)变化时,都会进行比较。

在这里插入图片描述
虽然组件 C 和组件 H 结构相似,但类型不同,React 不会进行比较,会直接删除组件 C,创建组件 H。

element diff:

单个节点的比较:同一层级的单个节点,通过标记 key 的方式来进行对比。

对于同一层级的单个节点,有三种操作,分别是移动、创建和删除,针对是否使用 key 标识可分为两种情况:

  1. 对于不使用 key 标识的情况:React 对同一层级相同位置的的新旧单个节点逐个进行对比,如果一致则复用,如果不一致则直接删除旧的创建新的。
    在这里插入图片描述
    React 对同一层级的新旧单个节点进行对比,发现新集合中的 B 不等于旧集合中的 A,于是删除 A,创建 B,依此类推,直到删除 D,创建 C。这会使得相同的节点不能复用,出现频繁的删除和创建操作,从而影响性能。
  2. 对于使用 key 标识的情况:React 会对新集合进行遍历,通过唯一的 key 来判断旧集合中是否存在相同的节点,如果没有则创建,如果有则判断是否需要进行移动操作。
    在这里插入图片描述

React Fiber:

reconciler:协调器。用来对 VNode 进行 diff。
renderer:渲染器。用来将 VNode 转为真实 DOM 。

为什么需要 Fiber?

React16 之前的 reconciler 是采用递归形式工作的,一旦开始就无法中断,否则就得重来。也就是说,在 setState 后,reconciler 需要将所有的 Virtual DOM 都递归遍历完成后,才能给出当前需要修改的真实 DOM 的信息,给到 renderer 进行渲染。

因此就会导致 React 从 setState 开始到渲染完成,整个过程是同步的,一气呵成。如果需要渲染的组件比较庞大,JS 执行占据主线程的时间就会较长,会导致页面响应度变差。

由于 JS 是单线程的,因此浏览器的 JS 引擎和渲染引擎是互斥的,当其中一个执行时,另一个只能挂起等待。
通常将 React16 之前的 reconciler 称为 stack reconciler,React16 之后的 reconciler 称为 fiber reconciler。

React Fiber 的原理:

因此在 React v16.0 中引入了 React Fiber 架构。Fiber 需要实现两个功能:

  1. 新的任务调度:将任务拆分成一个个小的任务,有高优先级任务的时候将浏览器让出来先执行高优先级的任务,等浏览器空闲了再继续执行之前的任务。

    React Fiber 将 Virtual DOM 的更新拆分成了多个小任务,每个小任务只更新部分组件的 DOM,并给每个任务分配了优先级;利用 requestIdleCallback API 在浏览器的空闲时间去执行小任务,每执行一个任务单元后,查看是否有其他高优先级的任务,如果有的话就先执行优先级高的任务。

    requestIdleCallback:当前浏览器处于空闲状态才会执行传入其中的回调函数。

  2. 新的数据结构:可以随时中断,下次进行可以接着执行。

    React Fiber 中是扁平的链表结构,每一个 React 元素对应一个 fiber,每个 fiber 中不仅包含着当前元素的信息,还保存着父节点的地址、第一个子节点的地址、下一个兄弟节点的地址。因此无论在哪个点执行中断,只要将下标存起来就行就能恢复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值