概述
React
的初学者在写React
项目的过程中,经常会在写一个列表组件的时候,发现控制台抛出了如下的Warning
Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of ServiceInfo. See [https://fb.me/react-warning-keys](https://fb.me/react-warning-keys) for more information.
提示每一个创建的子组件必须有key属性,那么这个key具体是做什么的呢?
在React中,key不是给用户使用的,而是给React自己使用的,一般来说,当我们动态创建React的元素,并且React元素内包含数量或顺序不确定的子元素时,我们就需要提供 key 这个特殊的属性。
Diff算法和key的关系
标准的diff算法时间复杂度是O(n^3)
,如果你的项目中有1000个元素,则需要1000000000次比对,这种效率是极其低下的,而React的diff算法不同,它的时间复杂度为O(n)
,但这种时间复杂度建立在两个基础之上:
- 不同类型的元素产生不同的虚拟DOM树
- 开发人员可以通过不同的key属性来标识哪些子元素在不同的渲染环境中需要保持稳定。
针对第一种情况:
// A组件
<div>
<Todos />
</div>
// B组件
<span>
<Todos />
</span>
如果我们现在想把A组件更新成B组件,React在做比较的时候,发现最外层节点类型不同,则直接废弃A组件,重新创建B组件,哪怕里面的子组件是一样的,虽然这是一种巨大的浪费,但为了达到O(n)
的时间复杂度,只能采用这种方式,在开发项目过程中,我们要避免更改节点的包裹类型
针对第二种情况
this.state = {
users: [{id:1,name: '张三'}, {id:2, name: '李四'}, {id: 2, name: "王五"}],
....//省略
}
render()
return(
<div>
<h3>用户列表</h3>
{this.state.users.map(u => <div key={u.id}>{u.id}:{u.name}</div>)}
</div>
)
);
上面的代码在DOM挂载渲染后,只会展示张三和李四两个用户,王五并没有展示,是因为李四的key值和王五相同,此时React会认为这是一个相同的组件,所以不去渲染。
所以在有了Key属性后,React就会通过key来决定是重新创建一个组件还是更新原来的组件。
- key值相同:组件属性发生变化,React只更新对应组件的属性
- key值不同:React先销毁原来的组件,然后在重新创建新的组件
key值要稳定唯一
在项目开发中,key属性的使用场景最多的还是由数组动态创建的子组件的情况 ,
我们要保证key要稳定唯一
可以把key看做每个人的身份中号,也可以看做数据库中的主键,都是用来作为唯一标识的。
因此,如果你采用下面的方式创建key值
{
this.state.data.map(el=><MyComponent key={Math.random()}/>)
}
是完全错误的,因为Math.random
随机生成,不稳定
尽量不要用数组的index去作为key
之所以这里说的是尽量,是因为如果你List中的子组件如果只是负责纯展示的话,(不涉及数组的动态变更),那么是可以采用index
作为key的。
但如果组件设计到数组的动态变更,例如数组新增元素、删除元素或者重新排序等,这时index作为key会导致展示错误的数据。
{this.state.data.map((v,index)=><Item key={index} v={v} />)}
// 开始时:['a','b','c']=>
<ul>
<li key="0">a <input type="text"/></li>
<li key="1">b <input type="text"/></li>
<li key="2">c <input type="text"/></li>
</ul>
//input输入框分别输入 1,2,3
// 数组重排 -> ['c','b','a'] =>
<ul>
<li key="0">c <input type="text"/></li>
<li key="1">b <input type="text"/></li>
<li key="2">a <input type="text"/></li>
</ul>
//input 输入框还是 1,2,3 并未随着数组重排而改变
在上面的代码中,key值采用index,在数据重新排列后:
- 组件重新render得到新的虚拟DOM
- 新的虚拟DOM和之前的虚拟DOM进行比较,发现都有key=0的组件,此时React认为这是同一个组件,因此只会更新组件,不会销毁在重新创建
- 然后比较children,发现文本内容不同 a⇒c 但input组件没发生变化,此时触发
componentWillReceiveProps
方法,从而更新其子组件文本内容 - 因为组件的children中input组件没有变化,其又与父组件传入的任props没有关联,所以input组件不会更新(即其
componentWillReceiveProps
方法不会被执行),导致用户输入的值不会变化
最后要特别说明的一个点是:
key不是用来提升react的性能的,不过用好key对性能是有帮组的