一个例子
有这样的一个场景如下图所示,有一组动态数量的input,可以增加和删除和重新排序,数组元素生成的组件用index
作为key的值,例如下图生成的ui展示:
上面例子中的input组件渲染的代码如下所示,全部完整代码可以参考 ==>完整code。
{this.state.data.map((v,idx)=><Item key={idx} v={v} />)}
//Item组件render方法
render(){
return <li>{this.props.v} <input type="text"/></li>
}
首先说明的是,若页面中数组内容是固定而不是动态的话,上面的代码也不会有什么问题(。•ˇ‸ˇ•。 但是这不是推荐的做法)。
但是,动态数组导致其渲染的组件就会有问题,从上面图中你也能看出问题:数组动态改变后,页面上input的输入内容跟对应的数组元素顺序不对应。
为什么会这样呢?本文后面会有解释。react初学者对这可能更加迷惑,本文就来跟大家探讨一下react的key用法,
react key概述
key的作用
react中的key属性,它是一个特殊的属性,它是出现不是给开发者用的(例如你为一个组件设置key之后不能获取组件的这个key props),而是给react自己用的。
那么react是怎么用key的呢?react的作者之一Paul O’Shannessy有提到:
Key is not really about performance, it’s more about identity (which in turn leads to better performance). Randomly assigned and changing values do not form an identity
简单来说,react利用key来识别组件,它是一种身份标识标识,就像我们的身份证用来辨识一个人一样。每个key对应一个组件,相同的key react认为是同一个组件,这样后续相同的key对应组件都不会被创建。例如下面代码:
//this.state.users内容
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渲染挂载后,用户列表只有张三
和李四
两个用户,王五
并没有展示处理,主要是因为react根据key认为李四
和王五
是同一个组件,导致第一个被渲染,后续的会被丢弃掉。
这样,有了key属性后,就可以与组件建立了一种对应关系,react根据key来决定是销毁重新创建组件还是更新组件。
- key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。
- key值不同,则react先销毁该组件(有状态组件的
componentWillUnmount会执行
),然后重新创建该组件(有状态组件的constructor
和componentWillUnmount
都会执行)
另外需要指明的是:
key不是用来提升react的性能的,不过用好key对性能是有帮组的。
key的使用场景
在项目开发中,key属性的使用场景最多的还是由数组动态创建的子组件的情况
,需要为每个子组件添加唯一的key属性值。
那么,为何由数组动态创建的组件必须要用到key属性呢?这跟数组元素的动态性有关。
拿上述用户列表的例子来说,看一下babel对上述代码的转换情况:
// 转换前
const element = (
<div>
<h3>用户列表</h3>
{[<div key={1}>1:张三</div>, <div key={2}>2:李四</div>]}
</div>
);
// 转换后
"use strict";
var element = React.createElement(
"div",
null,
React.createElement("h3",null,"用户列表"),
[
React.createElement("div",{ key: 1 },"1:张三"),
React.createElement("div",{ key: 2 },"2:李四")
]
);
有babel转换后React.createElement
中的代码可以看出,其它元素之所以不是必须需要key是因为不管组件的state
或者props
如何变化,这些元素始终占据着React.createElement
固定的位置,这个位置就是天然的key。
而由数组创建的组件可能由于动态的操作导致重新渲染时,子组件的位置发生了变化,例如上面用户列表子组件新增一个用户,上面两个用户的位置可能变化为下面这样:
var element = React.createElement(
"div",
null,
React.createElement("h3",null,"用户列表"),
[
React.createElement("div",{ key: 3 },"1:王五"),
React.createElement("div",{ key: 1 },"2:张三"),
React.createElement("div",{ key: 2 },"3:李四")
]
);
可以看出,数组创建子组件的位置并不固定,动态改变的;这样有了key属性后,react就可以根据key值来判断是否为同一组件。
另外,还有一种比较常见的场景:为一个有复杂繁琐逻辑的组件添加key后,后续操作可以改变该组件的key属性值,从而达到先销毁之前的组件,再重新创建该组件。
key的最佳实践
上面说到了,由数组创建的子组件必须有key属性,否则的话你可能见到下面这样的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 for more information.
可能你会发现,这只是warning而不是error,它不是强制性的,为什么react不强制要求用key而报error呢?其实是强制要求的,只不过react为按要求来默认上帮我们做了,它是以数组的index
作为key的。
index作为key是一种反模式
在list数组中,用key来标识数组创建子组件时,若数组的内容只是作为纯展示,而不涉及到数组的动态变更,其实是可以使用index
作为key的。
但是,若涉及到数组的动态变更,例如数组新增元素、删除元素或者重新排序等,这时index作为key会导致展示错误的数据。本文开始引入的例子就是最好的证明。
{this.state.data.map((v,idx)=><Item key={idx} 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>
// 数组重排 -> ['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"