- 🥇 虚拟DOM与DOM Diffing算法
- 📌 案例
- 📕 基本原理图
- 🔑 虚拟DOM中的key的作用
- 🔐用index作为key可能会引发的问题:
- 🔓开发中如何选择key?
首先来个案例验证一下Diffing算法的存在以及工作原理
需求:
验证虚拟DOM Diffing算法的存在
效果:
📕 基本原理图代码如下:
class Time extends React.Component {
state = { date: new Date() }
//Date 对象会自动把当前日期和时间保存为其初始值
componentDidMount() {
setInterval(() => {
this.setState({
date: new Date()
})
}, 1000);
}
render() {
return (
{/*
每次一更新数据变成新的时间,render重新渲染,
拿到的一堆虚拟DOM与之前的数据通过Diffing算法进行比较,
发现h1和input对应的真实DOM在页面上没有变化
,而span标签对应的真实DOM在页面上一直变化
*/}
hello
{/* toTimeString():把 Date 对象的时间部分转换为字符串: */}
现在是:{this.state.date.toTimeString()}
{/* 通过Diffing算法进行对比,发现这个input对应的真实DOM在页面上没有变化,
所以输入的数据不会丢失,因此Diffing算法不止对比一层的标签,
有多少层对比多少层,逐层对比,最小的力度是标签 */}
);
}
}
ReactDOM.render(, document.getElementById('test'))
代码解释:
- 首先初始化state,获取当前的日期时间,在组件挂载完毕之后,设置一个计时器,每一秒更新当前的时间,每次一更新数据变成新的时间,render重新渲染, 拿到的一堆虚拟DOM与之前的数据通过Diffing算法进行比较,发现h1和input对应的真实DOM在页面上没有变化,而span标签对应的真实DOM在页面上一直变化,说明一直更新span这个虚拟DOM,映射成真实DOM一直在变化。
- Diffing算法不仅仅只比较一层的节点,通过Diffing算法进行对比,发现span标签里的input对应的真实DOM在页面上没有变化,所以输入的数据不会丢失,因此
Diffing算法不止对比一层的标签
, 有多少层对比多少层,逐层对比,最小的力度是标签
。
🔒了解了Diffing算法的存在之后,接下来介绍一下当遍历数据时,虚拟DOM中的key的作用~~
🔑 虚拟DOM中的key的作用经典面试题:
- react/vue中的key有什么作用?(key的内部原理是什么?)
- 为什么遍历列表时,key最好不要用index?
-
简单的说:
key是虚拟DOM对象的标识
,在更新显示时key起着极其重要的作用。 -
详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
-
旧虚拟DOM中
找到了
与新虚拟DOM相同的key: a. 若虚拟DOM中内容
没变
,直接使用之前的真实DOM b. 若虚拟DOM中内容
变了
,则生成新的真实DOM,随后替换掉页面中之前的真实DOM -
旧虚拟DOM中
未找到
与新虚拟DOM相同的keya. 根据数据创建新的真实DOM,随后渲染到页面
-
-
若对数据进行:逆序添加,逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低。
举个案例🥜,效果如下:
代码如下:
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 (
展示人员信息
添加一个小王
使用index(索引值)作为key
{
this.state.persons.map((personObj, index) => {
return {personObj.name}----{personObj.age}
})}
);
}
}
ReactDOM.render(, document.getElementById('test'))
慢动作回放----使用index索引值作为key
初始数据:
{ id: 1, name: ‘小张’, age: 18 },
{ id: 2, name: ‘小李’, age: 19 }
初次挂载时,根据数据生成初始的虚拟DOM:
< < 小张----18 < <
< < 小李----19 < <
更新后的数据:
{ id: 3, name: ‘小王’, age: 20 },
{ id: 1, name: ‘小张’, age: 18 },
{ id: 2, name: ‘小李’, age: 19 }
更新后的数据的虚拟DOM:
< < 小王----20 < <
< < 小张----18 < <
< < 小李----19 < <
根据前面的作用分析,用index会打乱顺序,虽然界面效果没问题,
但是,这样导致没有必要的真实DOM更新,效率降低
如何解决上述问题呢?
可以将id值作为key
问题就解决了。
{
this.state.persons.map((personObj) => {
return {personObj.name}----{personObj.age}
})}
慢动作回放----使用id唯一标识作为key
初始数据:
{ id: 1, name: ‘小张’, age: 18 },
{ id: 2, name: ‘小李’, age: 19 }
初次挂载时,根据数据生成初始的虚拟DOM:
更新后的数据:
{ id: 3, name: ‘小王’, age: 20 },
{ id: 1, name: ‘小张’, age: 18 },
{ id: 2, name: ‘小李’, age: 19 }
更新后的数据的虚拟DOM:
-
如果结构中还包含
输入类的DOM
:会产生错误DOM更新 ===> 界面有问题。
我们先在每一个li标签里面加上一个input框:
render() {
return (
展示人员信息
添加一个小王
使用index(索引值)作为key
{
this.state.persons.map((personObj, index) => {
return {personObj.name}----{personObj.age}
})}
使用id(数据的唯一标识)索引值作为key
{
this.state.persons.map((personObj) => {
return {personObj.name}----{personObj.age}
})}
);
}
当我们在输入框里分别输入框对应的名字之后,点击添加小王,会发现用index作为key的数据乱了,而用id作为key的数据是正确的。
效果如下:
再次进行分析:
慢动作回放----使用index索引值作为key
初始数据:
{ id: 1, name: ‘小张’, age: 18 },
{ id: 2, name: ‘小李’, age: 19 }
初次挂载时,根据数据生成初始的虚拟DOM:
< < 小张----18 < < < <
< < 小李----19 < < < <
更新后的数据:
{ id: 3, name: ‘小王’, age: 20 },
{ id: 1, name: ‘小张’, age: 18 },
{ id: 2, name: ‘小李’, age: 19 }
更新后的数据的虚拟DOM:
< < 小王----20 < < < <
< < 小张----18 < < < <
< < 小李----19 < < < <
对比新旧虚拟DOM的第一条数据时,发现姓名年龄不一样,而input输入框这个节点一样,然后将旧的虚拟DOM对应的真实DOM给新的虚拟DOM复用,但是旧的虚拟DOM对应的真实DOM里面还残留了小张的信息。
将id作为key就避免了这个问题
注意!!!
如果不存在对数据的逆序添加,逆序删除等破坏性顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
- 最好使用每条数据的
唯一标识
作为key,比如id,手机号,身份证号,学号等唯一值。 - 如果确定只是
简单的展示数据
,用index也是可以的。