React之key详解

一个例子

有这样的一个场景以下图所示,有一组动态数量的input,能够增长和删除和从新排序,数组元素生成的组件用index做为key的值,例以下图生成的ui展现:javascript

上面例子中的input组件渲染的代码以下所示,所有完整代码能够参考 ==>完整codejava

{this.state.data.map((v,idx)=><Item key={idx} v={v} />)}

//Item组件render方法
render(){
   return <li>{this.props.v} <input type="text"/></li>
}

首先说明的是,若页面中数组内容是固定而不是动态的话,上面的代码也不会有什么问题(。•ˇ‸ˇ•。 可是这不是推荐的作法)。react

可是,动态数组致使其渲染的组件就会有问题,从上面图中你也能看出问题:数组动态改变后,页面上input的输入内容跟对应的数组元素顺序不对应。数据库

为何会这样呢?本文后面会有解释。react初学者对这可能更加迷惑,本文就来跟你们探讨一下react的key用法,segmentfault

react key概述

key的做用

react中的key属性,它是一个特殊的属性,它是出现不是给开发者用的(例如你为一个组件设置key以后不能获取组件的这个key props),而是给react本身用的。数组

那么react是怎么用key的呢?react的做者之一Paul O’Shannessy有提到:babel

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 identitydom

简单来讲,react利用key来识别组件,它是一种身份标识标识,就像咱们的身份证用来辨识一我的同样。每一个key对应一个组件,相同的key react认为是同一个组件,这样后续相同的key对应组件都不会被建立。例以下面代码:ide

//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会执行),而后从新建立该组件(有状态组件的constructorcomponentWillUnmount都会执行)

另外须要指明的是:

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"/></li>
</ul>

上面实例中在数组从新排序后,key对应的实例都没有销毁,而是从新更新。具体更新过程咱们拿key=0的元素来讲明, 数组从新排序后:

  • 组件从新render获得新的虚拟dom;
  • 新老两个虚拟dom进行diff,新老版的都有key=0的组件,react认为同一个组件,则只可能更新组件;
  • 而后比较其children,发现内容的文本内容不一样(由a--->c),而input组件并无变化,这时触发组件的componentWillReceiveProps方法,从而更新其子组件文本内容;
  • 由于组件的children中input组件没有变化,其又与父组件传入的任props没有关联,因此input组件不会更新(即其componentWillReceiveProps方法不会被执行),致使用户输入的值不会变化。

这就是index做为key存在的问题,因此不要使用index做为key

key的值要稳定惟一

在数组中生成的每项都要有key属性,而且key的值是一个永久且惟一的值,即稳定惟一。

在理想状况下,在循环一个对象数组时,数组的每一项都会有用于区分其余项的一个键值,至关数据库中主键。这样就能够用该属性值做为key值。可是通常状况下多是没有这个属性值的,这时就须要咱们本身保证。

可是,须要指出的一点是,咱们在保证数组每项的惟一的标识时,还须要保证其值的稳定性,不能常常改变。例以下面代码:

{
    this.state.data.map(el=><MyComponent key={Math.random()}/>)
}

上面代码中中MyComponent的key值是用Math.random随机生成的,虽然可以保持其惟一性,可是它的值是随机而不是稳定的,在数组动态改变时会致使数组元素中的每项都从新销毁而后从新建立,有必定的性能开销;另外可能致使一些意想不到的问题出现。因此:

key的值要保持稳定且惟一,不能使用random来生成key的值。

因此,在不能使用random随机生成key时,咱们能够像下面这样用一个全局的localCounter变量来添加稳定惟一的key值。

var localCounter = 1;
this.data.forEach(el=>{
    el.id = localCounter++;
});
//向数组中动态添加元素时,
function createUser(user) {
    return {
        ...user,
        id: localCounter++
    }
}

key其它注意事项

固然除了为数据元素生成的组件要添加key,且key要稳定且惟一以外,还须要注意如下几点:

  • key属性是添加到自定义的子组件上,而不是子组件内部的顶层的组件上。
//MyComponent
...
render() {//error
    <div key={{item.key}}>{{item.name}}</div>
}
...

//right
<MyComponent key={{item.key}}/>
  • key值的惟一是有范围的,即在数组生成的同级同类型的组件上要保持惟一,而不是全部组件的key都要保持惟一

  • 不只仅在数组生成组件上,其余地方也可使用key,主要是react利用key来区分组件的,相同的key表示同一个组件,react不会从新销毁建立组件实例,只可能更新;key不一样,react会销毁已有的组件实例,从新建立组件新的实例

{
  this.state.type ? 
    <div><Son_1/><Son_2/></div>
    : <div><Son_2/><Son_1/></div>
}

例如上面代码中,this.state.type的值改变时,原Son_1和Son2组件的实例都将会被销毁,并从新建立Son_1和Son_2组件新的实例,不能继承原来的状态,其实他们只是互换了位置。为了不这种问题,咱们能够给组件加上key。

{
  this.state.type ? 
    <div><Son_1 key="1"/><Son_2 key="2"/></div>
    : <div><Son_2 key="2" /><Son_1 key="1"/></div>
}

这样,this.state.type的值改变时,Son_1和Son2组件的实例没有从新建立,react只是将他们互换位置。

参考文献

相关文章
相关标签/搜索