浅谈ReactJS中key的作用 你真的知道吗

Posted H5前端开发社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈ReactJS中key的作用 你真的知道吗相关的知识,希望对你有一定的参考价值。

引言:浅谈ReactJS中key的作用 从一段代码开始

key概念
react 中的key 属性是一个特殊的属性,它的出现不适给开发者用的,(列如你为一个组件设置key 之后,也无法获取这个组件的key值, )而是给react 自己用的。
简单说,react利用key来识别组件,他是一种身份标识,就像每个人有一个身份证来做辨识一样。每个key 对应一个组件,相同的key react认为是同一个组件,这样后续相同的key对应组件都不会被创建。

key的使用场景
在项目开发中,key属性的使用场景最多的还是由数组动态创建的子组件情况,需要为每个子组件添加唯一的key属性值,那有的人会自然而然想到,key 和动态渲染的子元素获取的index的值很接近,是不是我们可以直接使用index值 赋值给key呢?
列如:

{dataList.map((item,index)=>{ return <div style={mystyle} key={index}>{item.name}</div> })}

在尝试后我们发现报错没了,渲染也没问题。但是这里我们强烈不推荐使用数组的index 值来作为key。
如果数据更新仅仅是数组重新排序或在其中间位置插入新元素,那么所有元素都将重新渲染。
例如:
本来index=2 的元素向前移动后,那该元素的key 不也同样发生了改变,那这样改变,key 就没有任何存在的意义了,既然是作为身份证一样的存在,那就不容有失,当然,在你用key值创建子组件的时候,若数组的内容只是作为纯展示,而不涉及到数组的动态变更,其实是可以使用index 作为key的,

key的值必须保证唯一且稳定
我们在与key值打过几次交到以后,感觉key值就类似于数据库中的主键id一样,有且唯一。

浅谈ReactJS中key的作用 你真的知道吗

再看下面这段代码:

function List(props){ let {data}=props; return ( <ul> { return data.map((item,index)=>{ return <li key = {index}>{item.name}</li> }) } </ul> ); }

上面是一段很常见的react遍历代码,通过遍历来根据数组内容渲染出实际需要的组件。
注意,我们在上面的每个li元素中加了一个属性:key。如果我们不加的话,react会在控制台抛出一段警告。那么这个key具体有什么作用。

diff算法

要回答这个问题,我们需要从react的diff算法开始讲起。react有着一套严密的算法来确保每次组件的所有变动都能及时的得到更新。这套算法不同于标准的Tree Diff算法,建立在以下两个假设的基础上,并将算法复杂度优化到O(n)(标准的Tree Diff算法复杂度为O(n3),意味着如果你的组件中有1000个元素,则需要1000000000次的比对,这个性能是无法承受的):
1、不同的元素类型生产不同的虚拟DOM树。
2、开发人员可以通过不同的key属性来标识哪些子元素在不同的渲染环境中需要保持稳定的。

第二个假设引申意思就是,如果两个元素有不同的key,那么在前后两次渲染中就会被认为是不同的元素,这时候旧的那个元素会被unmount,新的元素会被mount。换言之,如果两个元素是相同的key,且满足第一点元素类型相同,则会被认为是两个相同的元素。这一点不仅仅是适用于在元素遍历的时候,是整个react diff算法的前提。

例如在我们项目中有这样一段代码:

//更新前render(){ return ( <List key = '1'> );}//更新后render(){ return ( <List key = '2'> );}

这个时候,react就会把这个前一个List销毁之后重新构建一个List的实例(非必要情况下不要这么做,会有额外的性能开销)。
为什么我们在遍历生成元素的时候react要特别警告给元素加上key呢。

遍历生成元素的时候有什么不同

大部分时候,我们在做遍历的时候会返回出一组元素类型相同的子元素。这个过程其实并没有任何问题。问题在于我们对数据进行修改的时候(例如:向数组中插入一个元素,修改一个数组元素的值或则重新排序等),会有很大的不确定性,可能会给react的diff算法带来灾难。因为react在比较元素子元素是否相同的时候并不会精确查找元素具体的位置变动,只会在查到到不同之后对之后所有的元素全部执行一次dom更新操作。

//tree1 <ul> <li>1</li> <li>2</li></ul>//tree 2 <ul> <li>1</li> <li>3</li></ul>

react遇到这种情况的时候,只会修改第二个元素,通过ele.innerhtml = '3'的方法去更新dom,而不会去更新第一个子元素。这种情况下,性能开销会相对较小。但是如果遇到下面的情况,性能开销就大了。

//tree1 <ul> <li>1</li> <li>2</li></ul>//tree 2 <ul> <li>1</li> <li>3</li> <li>2</li></ul>

在上面的例子中我们试图在tree1中插入一个子元素。这时候react并不会执行插入操作,他直接会移除原先的第二个子元素,然后再append进去剩下的子元素,而其实我们这个操作只只需要一个insert操作就能完成。为了解决这种问题,react需要我们提供给一个key来帮助更新,减少性能开销。

再谈key的作用

在上面的例子中如果我们给每个li元素添加一个key属性情况就会得到优化。

//tree1 <ul> <li key='1'>1</li> <li key='2'>2</li></ul>//tree 2 <ul> <li key='1'>1</li> <li key='3'>3</li> <li key='2'>2</li></ul>

这个时候react就会通过key来发现tree2的第二个元素不是原先tree1的第二个元素,原先的第二个元素被挪到下面去了,因此在操作的时候就会直接指向insert操作,来减少dom操作的性能开销。

我们要如何选择key

大部分情况下我们要在执行数组遍历的时候会用index来表示元素的key。这样做其实并不是很合理。我们用key的真实目的是为了标识在前后两次渲染中元素的对应关系,防止发生不必要的更新操作。那么如果我们用index来标识key,数组在执行插入、排序等操作之后,原先的index并不再对应到原先的值,那么这个key就失去了本身的意义,并且会带来其他问题。
例如:数组a=['a','b','c'];这个时候原先的0对应的是'a',1对应的是'b',依次...,如果我们对数组进行依次reverse操作,那么这个时候0就对应成了'c',2变成了'a'。这样的导致的除了效率问题还可能会产生额外的bug。具体例子可以点击这里。你可以试着add几条数据之后在input框中输入一些值,然后点下order就会后发现问题所在。
所以我们在选择key的时候一定要选择能和数据一一对应的值。如果找不到这个值可以参考下面操作。

var key = 0;//可以是任何的id generatorfunction id(){ return String(++key);}//任意的数组或者待遍历的数据data.forEach((item)=>{ if(!item.id){ item.id = id; }})

通过上面的方法我们手动给数组的每个元素添加一个唯一的标识id。

几点提醒

1、key值一定要和具体的元素一一对应到。
2、尽量不要用数组的index去作为key。
2、永远不要试图在render的时候用随机数或者其他操作给元素加上不稳定的key,这样造成的性能开销比不加key的情况下更糟糕。

喜欢的话,就转发给身边的程序员朋友吧!

浅谈ReactJS中key的作用 你真的知道吗

浅谈ReactJS中key的作用 你真的知道吗


以上是关于浅谈ReactJS中key的作用 你真的知道吗的主要内容,如果未能解决你的问题,请参考以下文章

你真的知道C语言里extern "C" 的作用吗?

浅谈你知道手机软件存在的安全隐患吗

Web前端的学习路线,你真的知道吗?

浅谈JS中的JSON.stringify() 和 JSON.parse()

什么是后端开发?你真的知道吗?

如何访问'key'对象reactJS的对象属性