【10】基础知识:React - DOM的diffing算法

一、虚拟 DOM 中 key 的作用

react/vue 中的 key 有什么作用?key的内部原理是什么?

简单来说:

key 是虚拟 DOM 对象的标识,在更新显示时 key 起着极其重要的作用,提高渲染效率,防止渲染错误。

详细的说:

当状态中的数据发生变化时,React 会根据【新数据】生成【新的虚拟DOM】

随后 React 进行【新虚拟DOM】与【旧虚拟DOM】的 diff 比较,比较规则如下:

复制代码
1、【旧虚拟DOM】中找到了与【新虚拟DOM】相同的 key:

若虚拟 DOM 中内容没变,直接使用之前的真实 DOM

若虚拟 DOM 中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实 DOM

2、旧虚拟DOM中未找到与新虚拟DOM相同的key

根据数据创建新的真实 DOM,随后渲染到到页面

"就地复用" 策略:

复制代码
当数据项的顺序发生改变时,他不会移动页面上的 DOM 元素匹配数据项顺序的改变,而是简单的复用此处的元素

如果绑定了 key 属性,就可对当前 DOM 元素进行唯一标识,在进行数据更新渲染时,会基于 key 的变化重新排列 DOM 元素的顺序

二、用 index 作为 key 可能会引发的问题

界面效果没问题, 但效率低:

若对数据进行,逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实 DOM 更新

html 复制代码
慢动作回放----使用 index 索引值作为 key

根据 diffing 算法规则,所有 DOM 元素都需要都需要重生成新的真实 DOM:
存在相同key 0 和 1,但虚拟 DOM内容改成了,需要生成新的真实 DOM;
新增key2,需要生成新的真实 DOM。

初始数据:
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 19 }
初始的虚拟 DOM:
<li key=0>小张---18</li>
<li key=1>小李---19</li>

更新后的数据:
{ id: 3, name: '小王', age: 20 },
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 19 }
更新数据后的虚拟 DOM:
<li key=0>小王---20</li>
<li key=1>小张---18</li>
<li key=2>小李---19</li>

界面有问题:

如果结构中还包含输入类的 DOM,会产生错误 DOM 更新

复制代码
慢动作回放----使用 index 索引值作为 key (界面有问题)

根据 diffing 算法规则,内部 input "就地复用"策略顺序会错乱

初始数据:
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 19 }
初始的虚拟 DOM:
<li key=0>小张---18<input type="text"/></li>
<li key=1>小李---19<input type="text"/></li>

更新后的数据:
{ id: 3, name: '小王', age: 20 },
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小李', age: 19 }
更新数据后的虚拟 DOM:
<li key=0>小王---20<input type="text"/></li>
<li key=1>小张---18<input type="text"/></li>
<li key=2>小李---19<input type="text"/></li>

注意:

如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。

三、开发中如何选择key?

1、最好使用每条数据的唯一标识作为 key , 比如 id、手机号、身份证号、学号等唯一值。

2、如果确定只是简单的展示数据,用 index 也是可以的。

四、示例

html 复制代码
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>key的作用</title>
</head>
<body>
	<div id="test"></div>
	<!-- 引入react核心库 -->
	<script type="text/javascript" src="../js/react.development.js"></script>
	<!-- 引入react-dom -->
	<script type="text/javascript" src="../js/react-dom.development.js"></script>
	<!-- 引入babel -->
	<script type="text/javascript" src="../js/babel.min.js"></script>

	<script type="text/babel">		
		class Person extends React.Component {
			state = {
				persons: [
					{ id: 1, name: '小张', age: 18 },
					{ id: 2, name: '小李', age: 19 }
				]
			}

			add = () => {
				const { persons } = this.state
				const p = { id: persons.length + 1, name: '小王', age: 20 }
				this.setState({ persons: [p, ...persons] })
			}

			render() {
				return (
					<div>
						<h2>展示人员信息</h2>
						<button onClick={this.add}>添加一个小王</button>
						<h3>使用index(索引值)作为key</h3>
						<ul>
							{
								this.state.persons.map((personObj, index) => {
									return <li key={index}>{personObj.name}---{personObj.age}<input type="text" /></li>
								})
							}
						</ul>
						<hr />
						<hr />
						<h3>使用id(数据的唯一标识)作为key</h3>
						<ul>
							{
								this.state.persons.map((personObj) => {
									return <li key={personObj.id}>{personObj.name}---{personObj.age}<input type="text" /></li>
								})
							}
						</ul>
					</div>
				)
			}
		}
		
		ReactDOM.render(<Person />, document.getElementById('test'))
	</script>
</body>
</html>
相关推荐
古一|18 分钟前
Vue3中ref与reactive实战指南:使用场景与代码示例
开发语言·javascript·ecmascript
peachSoda720 分钟前
封装一个不同跳转方式的通用方法(跳转外部链接,跳转其他小程序,跳转半屏小程序)
前端·javascript·微信小程序·小程序
@PHARAOH32 分钟前
HOW - 浏览器兼容(含 Safari)
前端·safari
undefined在掘金3904142 分钟前
flutter 仿商场_首页
前端
少卿42 分钟前
react-native图标替换
前端·react native
熊猫钓鱼>_>1 小时前
TypeScript前端架构与开发技巧深度解析:从工程化到性能优化的完整实践
前端·javascript·typescript
JYeontu2 小时前
肉眼难以分辨 UI 是否对齐,写个插件来辅助
前端·javascript
fox_2 小时前
别再踩坑!JavaScript的this关键字,一次性讲透其“变脸”真相
前端·javascript
盛夏绽放2 小时前
uni-app Vue 项目的规范目录结构全解
前端·vue.js·uni-app
少卿2 小时前
React Native Vector Icons 安装指南
前端·react native