React中diffing算法
1:虚拟dom中key的作用?
-
1) 简单的说:
key
是虚拟DOM
对象的标识,在更新显示时起到极其重要的作用。 -
2) 详细的说:当状态中的数据发生变化时,
react
会更具【新数据】生成新的【新的虚拟DOM
】,然后React
将进行【新虚拟DOM
】与【旧虚拟DOM
】的diff
比较,比较规则如下:-
a)在【旧虚拟
DOM
】中找到了【新虚拟DOM】相同的key
:- 若虚拟
DOM
中内容没有改变,直接使用之前的【真实DOM
】. - 若虚拟
DOM
中的内容改变,则生成新的【真实DOM
】,随后替换掉页面上之前的真实DOM
- 若虚拟
-
b) 【旧虚拟
DOM
】中未找到与【新虚拟DOM
】相同的key
- 更具新数据生成新的真实
DOM
,然后渲染到页面上。
- 更具新数据生成新的真实
-
2:index
作为key
可能引发的问题?
- 1) 若对数据进行逆序删除、添加等破坏顺序操作:
- a)会产生没必要的【真实
DOM
】的更新,界面没有问题,但效率低。
- a)会产生没必要的【真实
<body>
<div id="test"></div>
<script type="text/babel">
// 创建类组件
class MyComponent extends React.Component {
state = {
persons:[
{name:'张三',age:20},
{name:'李四',age:22}
]
}
addPerson = ()=> {
let {persons} = this.state
let addDate = {name: '老王',age: persons.length+1}
this.setState({
persons: [addDate,...persons] // 把新添加的数据添加到数组的第一个位置
})
}
render() {
let {persons} = this.state
return (
<div>
<p><button onClick={this.addPerson}>添加一个老王</button></p>
<ul>
{
persons.map((item,index) => {
return (
<li key={index}>
<span>姓名:{item.name}----年龄:{item.age}</span>
</li>
)
})
}
</ul>
</div>
)
}
}
// 渲染
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>
</body>
分析:
- 1:数据
{name:'张三',age:20},
{name:'李四',age:22}
- 2:初始化的虚拟DOM
<li key=0> <span>姓名:张三----年龄:20</span> </li>
<li key=1> <span>姓名:李四----年龄:22</span> </li>
- 3:更新后的数据
{name:'老王',age:3},
{name:'张三',age:20},
{name:'李四',age:22}
- 4:更新后的虚拟DOM
<li key=0> <span>姓名:老王----年龄:3</span> </li>
<li key=1> <span>姓名:张三----年龄:20</span> </li>
<li key=2> <span>姓名:李四----年龄:22</span> </li>
- 5:去旧的虚拟DOM找,是否有相同的key
<li key=0> <span>姓名:张三----年龄:20</span> </li>
<li key=0> <span>姓名:老王----年龄:3</span> </li>
发现【新虚拟DOM
中的key
】和【旧虚拟DOM
中的key
】
都为0,相等则比较内容,但发现内容不相同,则生成新的【真实DOM】<li key=0> <span>姓名:老王----年龄:3</span> </li>
<li key=1> <span>姓名:李四----年龄:22</span> </li>
<li key=1> <span>姓名:张三----年龄:20</span> </li>
同样的,在key
为1的时候,【旧虚拟DOM】也存在,且内容不一样,那么也是以【新的虚拟DOM】为准,生成【新的真实DOM】
<li key=2> <span>姓名:李四----年龄:22</span> </li>
当key
等于2的时候,发现【旧虚拟DOM】中不存在key等于2的DOM,那么生成【新的真实DOM】 <li key=2> <span>姓名:李四----年龄:22</span> </li>
所以,本来以下这两个DOM元素是不必更新的,可以复用的,但却因为key的顺序已经被打乱了,导致页面的真实DOM被更新。
<li key=0> <span>姓名:张三----年龄:20</span> </li>
<li key=1> <span>姓名:李四----年龄:22</span> </li>
-
如果使用id唯一值作为key,会发生几次更新呢?
- 数据
{id:0,name:'张三',age:20}, {id:1,name:'李四',age:22}
- 2:初始化的虚拟DOM
<li key=0> <span>姓名:张三----年龄:20</span> </li> <li key=1> <span>姓名:李四----年龄:22</span> </li>
- 3:更新后的数据
{id:2,name:'老王',age:3}, {id:0,name:'张三',age:20}, {id:1,name:'李四',age:22}
- 4:更新后的虚拟DOM
<li key=2> <span>姓名:老王----年龄:3</span> </li> <li key=0> <span>姓名:张三----年龄:20</span> </li> <li key=1> <span>姓名:李四----年龄:22</span> </li>
- 5:去旧的虚拟DOM找,是否有相同的key
发现只有
<li key=2> <span>姓名:老王----年龄:3</span> </li>
不一样,需要更新【真实DOM】。但其他两个是一样的,且内容也一样,可以复用不必更新DOM。所以,更新的次数就少了很多,效率得到提高。
-
2) 如果结构中还包括输入类的
DOM
操作:render() { let {persons} = this.state return ( <div> <p><button onClick={this.addPerson}>添加一个老王</button></p> <ul> { persons.map((item,index) => { return ( <li key={index}> <span>姓名:{item.name}----年龄:{item.age}</span> <input type="text"/> // 加了一个输入框 </li> ) }) } </ul> </div> ) }
- a)会产生错误
DOM
更新,页面会有问题
数据发生错乱,那是因为在比较
<input type="text"/>
的时候,标签和属性都是一样的,那么就会复用,但是输入框依然残留有之前的数据,所以,我们看到输入框不变内容依旧不变。 - a)会产生错误
-
3) 注意: 如果不对数据进行逆序删除、逆序添加等破坏顺序操作,仅仅用于渲染列表展示,那么
index
作为key
是没有任何问题的。
3:开发中如何选择key
?
-
最好使用每条数据唯一的标识作为
key
,比如ID、身份证号、学号等唯一值。 -
如果只是简单的数据展示,使用
index
也是可以的。