React
使用Diff
算法来比较虚拟DOM
树和真实DOM
树之间的差异,并仅更新必要的部分,以提高性能。key
的作用是在Diff
算法中帮助React
确定哪些节点已更改,哪些节点已添加或删除。
我们以案例来说明。
class Person extends React.Component {
state = {
persons: [
{id:1,name:'张三',age: 20},
{id:2,name:'李四',age: 21},
]
}
// 增加人员
addPerson = () => {
let {persons} = this.state
const user = {id:persons.length + 1,name:'王麻子',age:22}
persons = [user,...persons]
this.setState({persons})
}
render(){
const {persons} = this.state
return (
<div>
<h1>验证diff算法</h1>
<button onClick={this.addPerson}>增加人员</button>
{
persons.map((val,idx)=> {
const info = "姓名:"+val.name+ "===年龄:" + val.age
console.log(idx,info)
return (
<div key={idx}>{info}</div>
)
})
}
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Person/>, document.getElementById('app'))
以上代码我们使用的是索引值idx
来作为标签的key值,渲染到页面,当我点击【增加人员】的按钮时,会在persons
的状态值里面最前方插入一个新的人员信息,然后react
在render
到页面中去。其页面效果如下:
class Person extends React.Component {
state = {
persons: [
{id:1,name:'张三',age: 20},
{id:2,name:'李四',age: 21},
]
}
// 增加人员
addPerson = () => {
let {persons} = this.state
const user = {id:persons.length + 1,name:'王麻子',age:22}
persons = [user,...persons]
this.setState({persons})
}
render(){
const {persons} = this.state
return (
<div>
<h1>验证diff算法</h1>
<button onClick={this.addPerson}>增加人员</button>
{
persons.map((val,idx)=> {
const info = "姓名:"+val.name+ "===年龄:" + val.age
console.log(idx,info)
return (
<div key={val.id}>{info}</div>
)
})
}
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Person/>, document.getElementById('app'))
以上代码我们使用id
来作为标签的key
值,但是这里的效果和我们看到的是一样的,但是在react
中处理的方式是不一样的,后续我们继续讨论。
我们对以上案例做一下修改,我们增加一个输入框,在看看其效果。
class Person extends React.Component {
state = {
persons: [
{id:1,name:'张三',age: 20},
{id:2,name:'李四',age: 21},
]
}
// 增加人员
addPerson = () => {
let {persons} = this.state
const user = {id:persons.length + 1,name:'王麻子',age:22}
persons = [user,...persons]
this.setState({persons})
}
render(){
const {persons} = this.state
return (
<div>
<h1>验证diff算法</h1>
<button onClick={this.addPerson}>增加人员</button>
{
persons.map((val,idx)=> {
const info = "姓名:"+val.name+ "===年龄:" + val.age
console.log(idx,info)
return (
<div key={idx}>{info} <input defaultValue={info} type="text"/></div>
)
})
}
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Person/>, document.getElementById('app'))
直接看效果:
姓名:王麻子===年龄:22 ----输入框:姓名:张三===年龄:20
姓名:张三===年龄:20 ----输入框:姓名:李四===年龄:21
姓名:李四===年龄:21 ----输入框:姓名:李四===年龄:21
在以上效果图上我们发现了严重的错误:输入框的内容与其人员信息不一致,这是为什么呢?
class Person extends React.Component {
state = {
persons: [
{id:1,name:'张三',age: 20},
{id:2,name:'李四',age: 21},
]
}
// 增加人员
addPerson = () => {
let {persons} = this.state
const user = {id:persons.length + 1,name:'王麻子',age:22}
persons = [user,...persons]
this.setState({persons})
}
render(){
const {persons} = this.state
return (
<div>
<h1>验证diff算法</h1>
<button onClick={this.addPerson}>增加人员</button>
{
persons.map((val,idx)=> {
const info = "姓名:"+val.name+ "===年龄:" + val.age
console.log(idx,info)
return (
<div key={val.id}>{info} <input defaultValue={info} type="text"/></div>
)
})
}
</div>
)
}
}
// 2、将虚拟DOM渲染到页面,标签必须闭合
ReactDOM.render(<Person/>, document.getElementById('app'))
直接看效果:
姓名:王麻子===年龄:22 ----输入框:姓名:王麻子===年龄:22
姓名:张三===年龄:20 ----输入框:姓名:张三===年龄:20
姓名:李四===年龄:21 ----输入框:姓名:李四===年龄:21
在以上效果图上我们发现了人员信息与输入框的信息一致,并没有发生什么错误,这是为什么呢?
根据上图我们可以知道使用索引值作为key的时候,三条数据基本上都要生成新的DOM,而输入框的值因为与旧的虚拟DOM比较内容一致,导致与新的数据不一致的结果。
根据上图我们可以知道使用唯一ID作为key值时,比较第一条数据时key值就不存在需要生成新的虚拟DOM,而后面两条的key值与旧的虚拟DOM一致,可以复用旧的真实DOM且不需要生成新的DOM,减少成本,这样使得性能更好。
结果案例演示我们知道为什么遍历列表时,key最好不要用index,而是使用唯一标识,以此来减少成本,提高性能。
1、简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2、详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key,根据数据创建新的真实DOM,随后渲染到到页面
1、若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2、如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3、注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
1、最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2、如果确定只是简单的展示数据,用index也是可以的。