相信初学者在学习React文档时都会注意到列表组件,并且官方会提醒列表组件需要key属性,虽然没有key属性React也能渲染出来,但会在控制台出现相应的warning。

为何需要key属性

由于React是基于Virtual DOM的diff算法来判断是否重新渲染组件,在列表组件数量比较多,但变化实际不多的情况(比如只插入一个),如果没有key属性辅助判断,需要重新加载其后所有的组件,这样对组件的渲染性能来说是一个灾难。

key值也就相当于列表组件的身份标识,React会根据该属性是否变化来判断组件是否更改,从而决定如何更新组件:

  • key相同,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新。
  • key值不同,则react先销毁该组件(有状态组件的componentWillUnmount会执行),然后重新创建该组件(有状态组件的constructorcomponentWillUnmount都会执行)1

我们来看这一段代码,代码定义了8秒后刷新列表内容,我们在8秒内在子组件输入组件的英文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class List extends Component {
constructor (props) {
super(props);
this.state = {
list: ['🍎', '🍌']
};
this.timerId = setTimeout(() => {
this.setState({
list: ['🍓', '🍌', '🍎']
});
clearTimeout(this.timerId);
}, 8000);
}
render () {

return (
<ul className='text'>
{
this.state.list.map((key, index) => {
return (
<ListItem key={index} value={key}/>
);
})
}
</ul>
);
}
}
class ListItem extends Component {
componentWillMount () {
console.log(this.props.value, 'will mount');
}
componentDidMount () {
console.log(this.props.value, 'did mount');
}
componentWillUpdate () {
console.log(this.props.value, 'will update');
}
componentDidUpdate () {
console.log(this.props.value, 'did update');
}
render () {
return (
<li>
组件 {this.props.value}
<input style={{marginLeft: '20px'}} />
</li>
);
}
}

使用index作为key属性,

看看在列表更新后,React会如何更新显示结果:

可以看出,按照索引重新加载的组件认为组件没变,只是属性发生改变,所以内部输入框的内容没有发生改变,但这一般来说并不是我们想要的,从打印出的log也能看出,列表组件的其实与内部内容已经无法对应了。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用字索引作为key属性
// 更新前
🍎 will mount
🍌 will mount
🍎 did mount
🍌 did mount
// 更新后
🍎 will update
🍌 will update
🍎 will mount
🍓 did update
🍌 did update
🍎 did mount

那么使用列表中的内容作为key属性呢

可以看出,列表组件的刷新与内部内容是对应的

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用字符串内容作为key属性
// 更新前
🍎 will mount
🍌 will mount
🍎 did mount
🍌 did mount
// 更新后
🍓 will mount
🍌 will update
🍎 will update
🍓 did mount
🍌 did update
🍎 did update

用什么值作为key属性

在充分理解这个属性之前,一直习惯用数组的索引作为key,竟然也没有深入地去想过这个问题,直到被面试官问到,惭愧。

一般来说,如果纯粹是展示组件,且仅与父组件状态相关,其实使用索引与内容作为key属性问题并不大;

如果涉及到较多的状态更新或自身携带状态,应该使用能够稳定、唯一化、与内容严格对应的key属性。

[1] http://www.cnblogs.com/wonyun/p/6743988.html