详解React之key的使用和实践


Posted in Javascript onSeptember 29, 2018

在渲染列表时,React的差异比较算法需要一个在列表范围内的唯一key来提高性能(通常用于获知哪个列表项改变了)。这个唯一的key需要我们手动提供。React官方建议使用列表数据中可用于唯一性标识的字段来作为列表项渲染时的key。如果实在没有,则可使用数组的index勉为其难,性能上可能会打折扣。

一个例子

有这样的一个场景如下图所示,有一组动态数量的input,可以增加和删除和重新排序,数组元素生成的组件用index作为key的值,例如下图生成的ui展示:

详解React之key的使用和实践

上面例子中的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会执行),然后重新创建该组件(有状态组件的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只是将他们互换位置。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
来自国外的14个图片放大编辑的jQuery插件整理
Oct 20 Javascript
点击进行复制的JS代码实例
Aug 23 Javascript
Javascript异步编程模型Promise模式详细介绍
May 08 Javascript
CSS3实现动态背景登录框的代码
Jul 28 Javascript
javascript中的try catch异常捕获机制用法分析
Dec 14 Javascript
jquery+html仿翻页相册功能
Dec 20 Javascript
jQuery插件zTree实现获取当前选中节点在同级节点中序号的方法
Mar 08 Javascript
React通过父组件传递类名给子组件的实现方法
Nov 13 Javascript
详解vue2.0监听属性的使用心得及搭配计算属性的使用
Jul 18 Javascript
简单了解Javscript中兄弟ifream的方法调用
Jun 17 Javascript
vuex 多模块时 模块内部的mutation和action的调用方式
Jul 24 Javascript
Canvas三种动态画圆实现方法说明(小结)
Apr 16 Javascript
vue给组件传递不同的值方法
Sep 29 #Javascript
vue动画之点击按钮往上渐渐显示出来的实例
Sep 29 #Javascript
浅谈vue项目打包优化策略
Sep 29 #Javascript
vue里input根据value改变背景色的实例
Sep 29 #Javascript
4个顶级开源JavaScript图表库
Sep 29 #Javascript
脚手架vue-cli工程webpack的基本用法详解
Sep 29 #Javascript
vue实现条件判断动态绑定样式的方法
Sep 29 #Javascript
You might like
人族 TERRAN 概述
2020/03/14 星际争霸
PHP下判断网址是否有效的代码
2011/10/08 PHP
有关phpmailer的详细介绍及使用方法
2013/01/28 PHP
PHP设计模式之责任链模式的深入解析
2013/06/13 PHP
PHP5中GD库生成图形验证码(有汉字)
2013/07/28 PHP
php实现扫描二维码根据浏览器类型访问不同下载地址
2014/10/15 PHP
PHP 自动加载的简单实现(推荐)
2016/08/12 PHP
Laravel 5.3 学习笔记之 安装
2016/08/28 PHP
ThinkPHP5实现作业管理系统中处理学生未交作业与已交作业信息的方法
2016/11/12 PHP
Jquery下attr和removeAttr的使用方法
2010/12/28 Javascript
jQuery实现 注册时选择阅读条款 左右移动
2013/04/11 Javascript
禁用Tab键JS代码兼容Firefox和IE
2014/04/18 Javascript
jQuery对象的selector属性用法实例
2014/12/27 Javascript
轻松搞定jQuery.noConflict()
2016/02/15 Javascript
jQuery实现响应鼠标事件的图片透明效果【附demo源码下载】
2016/06/16 Javascript
JavaScript简单获取系统当前时间完整示例
2016/08/02 Javascript
jQuery Easyui快速入门教程
2016/08/21 Javascript
微信小程序 利用css实现遮罩效果实例详解
2017/01/21 Javascript
JavaScript实现公历转农历功能示例
2017/02/13 Javascript
利用原生JS与jQuery实现数字线性变化的动画
2017/02/24 Javascript
JS中使用 after 伪类清除浮动实例
2017/03/01 Javascript
javascript 开发之百度地图使用到的js函数整理
2017/05/19 Javascript
使用vue-router完成简单导航功能【推荐】
2018/06/28 Javascript
vue发送websocket请求和http post请求的实例代码
2019/07/11 Javascript
新手该如何学python怎么学好python?
2008/10/07 Python
python实现查询苹果手机维修进度
2015/03/16 Python
python小程序基于Jupyter实现天气查询的方法
2020/03/27 Python
python网络编程之五子棋游戏
2020/05/14 Python
python 爬取B站原视频的实例代码
2020/09/09 Python
Elemis美国官网:英国的第一豪华护肤品牌
2018/03/15 全球购物
波兰灯具、照明和LED购物网站:Lampy.pl
2019/03/11 全球购物
给分销商的致歉信
2014/01/14 职场文书
七年级历史教学反思
2014/02/05 职场文书
承诺书的格式范文
2014/03/28 职场文书
入党积极分子学习优秀共产党员先进事迹思想汇报
2014/09/13 职场文书
绵山导游词
2015/02/05 职场文书