说到key值,学过vue的童鞋,对此并不陌生。
我们在面试的时候也是会经常被问到类似问题:
1、在React/vue中key值得作用或者工作原理。
2、为什么在遍历数据时,不使用index索引值作为key?
3、开发中如何选择key值?
diffing算法
要讲key,避无可避就需要先了解下diffing算法。
看下面栗子:
class Time extends React.Component{
state={
date:new Date()
}
componentDidMount(){
setInterval(()=>{
this.setState({
date:new Date()
})
},1000)
}
render(){
return (
<div>
<h1>Hello World!^-^</h1>
<div><input type="text"/></div>
<h2>
现在是北京时间:{this.state.date.toTimeString()}
</h2>
</div>
)
}
}
ReactDOM.render(<Time/>,document.getElementById('test'))
这是个时间组件,每隔一秒就会更新页面中的时间。
在input框里输入文字,我们发现,随着时间文字的变化,输入框里的文字并没有变化,是怎么回事呢?
简单来说,在每一次更新数据后 ,h1和div下的input的DOM没发生变化,这时候就没有触发生成新的虚拟dom。
而h2里的时间,每隔一秒就做了一次setState的更新,那么整个h2标签都会重新渲染。
这里要注意,react的渲染最小颗粒度是标签。
那如果在h2里,再放一个input框,会怎样?
<h2>
现在是北京时间:{this.state.date.toTimeString()}
<input type="text"/>
</h2>
我们发现,h2内部的input框也没有发生变化。这说明,diffing算法并不是指计算最外层的。它会从外到里每一层去过滤,如果有标签存在就去做对比。
话锋硬转~,我们回到最开始的问题。先仔细读一下下方的代码示例。
class Person extends React.Component{
state = {
persons:[
{id:1,name:'小张',age:17},
{id:2,name:'小黄',age:19},
]
}
add= ()=>{
let {persons} = this.state
const p = {id:3,name:'小杨',age:20}
this.setState({persons:[p,...persons]})
}
render(){
const {persons} = this.state
return (
<div>
<h1>学生信息展示</h1>
<button onClick={this.add}>添加新学生</button>
<hr/>
<ul>
{
persons.map((item,index)=>{
return <li key={index}>姓名:{item.name} 年龄:{item.age}<input type="text" /></li>
})
}
</ul>
</div>
)
}
}
ReactDOM.render(<Person/>,document.getElementById('test'))
1、虚拟DOM中key的作用:
key是react/vue渲染从虚拟dom渲染真实dom时的唯一标识。合理的使用key,可以提高渲染效率。
工作原理:当状态中的数据发生变化时,react会根据新数据生成【新的虚拟dom】,随后,react进行【新虚拟dom】与【旧虚拟dom】的diff对比,比较规则如下:
a.旧虚拟dom中找到了与新虚拟dom相同的key值:
(1)若虚拟dom中的内容没变,直接使用之前的真实dom
(2)若虚拟dom中的内容变了,则生成新的真实DOM
b.旧虚拟dom中未找到与新虚拟dom相同的key:
根据数据创建新的真实DOM
2、用index作为key可能会引发问题:
a.若对数据进行:逆序添加、逆序删除等破坏顺序的操作:
示例中,展示了学生信息,并且以index为key值。当点击按钮时,向数据列表首位插入一条新数据。我们发现并没有什么影响,页面信息显示正常。
但如果,加入上边的规则分析一下,就会发现,这里有很大的效率问题。
初始数据:
* 初始数据:
* {id:1,name:'小张',age:17},
* {id:2,name:'小黄',age:19}
* 初始虚拟dom
* <li key=0>id:1 姓名:小张 年龄:17</li>
* <li key=1>id:2 姓名:小黄 年龄:19</li>
* 更新后的DOM
* <li key=0>id:3 姓名:小杨 年龄:20</li>
* <li key=1>id:1 姓名:小张 年龄:17</li>
* <li key=2>id:2 姓名:小黄 年龄:19</li>
* 在key值对比过程中,依照上边的规则,所有的旧真实dom全部被替换。
如果使用数据唯一标识作为key会怎样?
* 初始数据:
* {id:1,name:'小张',age:17},
* {id:2,name:'小黄',age:19}
* 初始虚拟dom
* <li key=1>id:1 姓名:小张 年龄:17</li>
* <li key=2>id:2 姓名:小黄 年龄:19</li>
* 更新后的DOM
* <li key=3>id:3 姓名:小杨 年龄:20</li> //只有第一条是重新渲染的真实DOM
* <li key=1>id:1 姓名:小张 年龄:17</li> //以下两条都可以在旧的虚拟dom中找到相同的key值,并且内容不便。真实dom沿用旧的。
* <li key=2>id:2 姓名:小黄 年龄:19</li>
b.如果结构中还包含输入类的DOM,会产生错误的dom更新->界面有问题。
* 初始虚拟dom
* <li key=0>id:1 姓名:小张 年龄:17<input type="text"></li>
* <li key=1>id:2 姓名:小黄 年龄:19<input type="text"></li>
* 更新后dom
* <li key=0>id:3 姓名:小杨 年龄:20<input type="text"></li>
* <li key=1>id:1 姓名:小张 年龄:17<input type="text"></li>
* <li key=2>id:2 姓名:小黄 年龄:19<input type="text"></li>
* 当数据发生变化时,首先对比最外层标签li的key值,发现在旧虚拟dom中存在key为0的dom,之后对比内容,内容不一样。然后再对比内部的input的标签,发现input的标签没发生变化。
* 那么更新的dom结构里不包含input的框,继续沿用。
* 所以就会有input框内值错位的现象。
c.注意!如果不存在对数据的逆序添加、逆序删除等破坏数据顺序的操作。仅用于渲染列表,使用index作为key值没有问题。
3、开发中如何选择key?
a、最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值。
b、如果确定只是简单的展示数据,用index也是可以的。