React学习笔记--虚拟DOM与DOM Diffing算法

本文详细解析了React的虚拟DOM结构和Diffing算法,包括逐层比较、同类型组件元素对比、同一类型元素对比、子节点递归比较以及key的作用。通过例子展示了Diffing算法如何高效更新UI,强调了正确使用key对于优化性能的重要性。
摘要由CSDN通过智能技术生成

React Diff原理深入分析

在了解Diff前,先看下React的虚拟DOM的结构

这是html结构
在这里插入图片描述
这是React渲染html时的js代码 自己可以在babel上试试
在这里插入图片描述
由此可以看出这是一个树结构
在这里插入图片描述
React在调用render方法时会创建一颗树(简称pre),在下一次调用render方法时会返回一颗不同的树(简称cur)。
React就会比较pre和cur这两棵树之间的差别来判断如何高效的更新UI,保证当前UI与最新的树cur保持同步。

为了高效更新UI,React在以下两个假设的基础上提出了一套O(n)的启发算法:

1.两个不同类型的元素会产生出不同的树;

2.开发者可以通过设置 key 属性,来告知渲染哪些子元素在不同的渲染下可以保存不变;

1、 Diffing 算法

1)逐层比较

在对比两棵树时,React是逐层进行比较的,只会对相同颜色框内的DOM节点进行比较
在这里插入图片描述
首先比较两棵树的根节点,不同类型的根节点会有不同的形态。当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。举个例子,当一个元素从 <a> 变成 <img>,从 <Article> 变成 <Comment>,或从 <Button> 变成 <div> 都会触发一个完整的重建流程。
在这里插入图片描述
React会销毁App组件(该组件的子组件也全都销毁),并且重新创建一个新的App组件(也包括App的子组件)。

如下的DOM结构转换:
在这里插入图片描述
React只会简单的考虑同层节点的位置变换,对于不同层的节点,只有简单的创建和删除。当根节点发现子节点中A不见了,就会直接销毁A;而当D发现自己多了一个子节点A,则会创建一个新的A作为子节点。因此对于这种结构的转变的实际操作是:
在这里插入图片描述
虽然看上去这样的算法有些“简陋”,但是其基于的是第一个假设:两个不同类型的元素会产生出不同的树。根据React官方文档,这一假设至今为止没有导致严重的性能问题。这当然也给我们一个提示,在实现自己的组件时,保持稳定的DOM结构会有助于性能的提升。例如,我们有时可以通过CSS隐藏或显示某些节点,而不是真的移除或添加DOM节点。

2)、对比同类型的组件元素

当一个组件更新时,组件实例会保持不变,但是state或props中数据的变化会调用render从而改变该组件中的子元素进行更新。

3)、对比同一类型的元素

当对比两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。
在这里插入图片描述
React将div的className由before修改为after(类似于React中更新state的合并操作)

4)、对子节点进行递归

默认情况下(逐层比较),当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个 mutation(突变)。
所以在列表末尾新增元素时,更新开销比较小。例如:
在这里插入图片描述
React 会先匹配两个 <li>first</li> 对应的树,然后匹配第二个元素 <li>second</li> 对应的树,最后插入第三个元素的 <li>third</li> 树。
如果只是简单的将新增元素插入到表头,那么更新开销会比较大。比如:
在这里插入图片描述
React 并不会意识到应该保留 <li>Duke</li> 和 <li>Villanova</li>,而是会重建每一个子元素。这种情况会带来性能问题。

5)、Keys

为了解决上述问题,React 引入了 key 属性。当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。以下示例在新增 key 之后,使得树的转换效率得以提高:
在这里插入图片描述
现在 React 知道只有带着 ‘2014’ key 的元素是新元素,带着 ‘2015’ 以及 ‘2016’ key 的元素仅仅移动了。所以只是创建了 key=2014的元素,并不会创建剩下的两个元素。

所以key的取值最好不要使用数组的下标,因为数组的顺序可能会发生变化,最好使用自身数据携带的唯一标识(id或是其它的属性)。

2、用例子验证一下Diffing算法

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>验证diff算法</title>
</head>
<body>
	<!-- 准备好一个“容器” -->
	<div id="test"></div>
	
	<!-- 引入react核心库 -->
	<script type="text/javascript" src="../js/17.0.1/react.development.js"></script>
	<!-- 引入react-dom,用于支持react操作DOM -->
	<script type="text/javascript" src="../js/17.0.1/react-dom.development.js"></script>
	<!-- 引入babel,用于将jsx转为js -->
	<script type="text/javascript" src="../js/17.0.1/babel.min.js"></script>

  <script type="text/babel">
  
  
		class Time extends React.Co
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值