React-key的作用

React-key

Posted by limantang on August 3, 2018

React-key

昨天遇到一个bug,bug的原因是因为不了解react-key的机制导致的,在此做出记录和大家分享一下. 在react里面key这个属性我们开发过程中有时候会使用到,但是它只可写,不可读,在react里key这个属性是为了react自己的性能优化内部使用的一个属性,它是react自己使用的. 其实这个key简单的来说就是 react利用这个属性来识别组件,来作为一种唯一的身份标识 每个组件对应一个key,如果这个key相同react就会认为是同一个组件,react会根据这个key来做出后续更新组件还是重新创建这个组件的操作

例如

    this.state {
        name: [ one: { id: 1, name: li }, two: { id: 2, name: man }, three: { id: 2, name: tang } ]
    }
    render (
        this.state.name.map(function (item) {
            <div key={ item.id }>{item.name} </div>
        }
    )

在这个过程中其实第三个div也就是 name=‘tang’ 的这个div是不会渲染出来的 因为react内部认为第二个和第三个key相同, 也就是同一个元素,所以只会渲染第二个,放弃渲染第三个 有了这个key属性之后, 这个key就跟组件或者说是元素之间建立一种对应关系, react根据key来判断是更新组件(元素)还是销毁后重新创建组件(元素)

  1. 如果key相同,如果组件属性发生了变化,则react只更新对应变化的属性,没有变化就不会更新
  2. 如果key不同,react则会先销毁原有组件(如果这个组件存在状态(state)则会执行componentWillUnMount),然后会重新创建该组件(如果组件存在状态的话则 constructorcomponentWIllUnMount都会执行)

Tips key这个属性对于我们开发人员来说不会存在对性能的提升, 但是react内部会使用它来优化性能

key的使用场景

一般来说我们使用key都是在动态的创建列表(通过数组)等类似的组件的时候会使用key这个属性 为什么通过数组动态的创建组件(元素)需要key这个属性呢?

    const = renderList = (
        <div>
            <h3>标题</h3>
            { 
                <div key={ 1 }>内容1</div>
                <div key={ 2 }>内容2</div>
             }
        </div>
    )
    // 转义之后
    var renderList = React.creatElement(
        div,
        null,
        React.creatElement( h3, null, “标题” ),
        [
        React.createElement( div, { key: 1 }, “内容1 ),
        React.creactElement( div, { key: 2 }, “内容2" )
        ]
    )

从上面转义之后的代码可以看出,不需要的key的<h3>元素以及在外层的<div>元素不需要key是因为它们占据的位置就是天然的key,无论其state或者props如何变化,其位置是不会发生改变的. 但是由数组动态创建的就不一定了,如果我们将数组元素减少,增加,或者重新排序,都会改变位置,所以我们必须添加key来让react知道组件(元素)的唯一性,react才知道应该如何对它们进行处理,前文提到了根据key的情况作出不同的处理. 更复杂的应用场景可以根据同一组件(元素)key的变化来达到销毁组件的目的. 我们实际应用的时候可能会发现就算我们没有给这种动态创建的列表添加key的时候也可以使用,不会报错,只是会产生警告,这是因为react内部帮我们把添加key的这一步做好了,就是根据数组的index,但是我们实际应用的时候还是要根据需求来构造key,以免出现意外的bug.

拓展

index作为key的反模式

如果数组只是用作纯展示功能其实可以用index当做key值,但是如果是动态数组就不要使用index当做key

我们的需求是一个用户的姓氏和名字的输入的组件

    this.state.name.map( 
        function (item, index) { 
            <Item key={ index } value={ name.title } />
        }
    )
    //开始的时候数组的顺序: [ { title: “姓氏” }, { title: “名字" } ]
    <ul>
        <li key={ 0 }>姓氏 <input type=“text"/> </li>
        <li key={ 1 }>名字 <input type=“text”/> </li>
    </ul>
    
    //数组变化之后的顺序: [ { title: “名字” }, { title: “姓氏" } ]
    <ul>
        <li key={0}>名字 <input type=“text"/> </li>
        <li key={1}>姓氏 <input type=“text”/> </li>
    </ul>
    
    //这时候的问题就是第一次的时候第一个input输入的是姓氏, 第二个input输入的是名字
        //第二次的时候第一个input输入的依然是姓氏, 第二个input输入的依然是名字
        //这个就没有达到我们想要的效果

原因 在上面的数组发生变化之后,key对应的实例并没有销毁,而是重新更新了, 因为key没有变化,具体的过程我们只拿第一个li举例说明:

1.组件重新渲染得到虚拟dom 2.新老虚拟dom进行diff, 新老版本都含有key,并且 key都等于0,此时react认为组件没有变化,则只会更新组件 3.然后继续深入比较这个li的children, 发现文本内容发生了变化, 但是input元素没有变化,这个时候触发componentWillReceiveProps从而更新文本内容 4.但是其children的input并没有发生变化,因为这个input并没有接受到props,所以并不会更新(componentWillReceiveProps也就不会执行), 所以不会更新, 最终也就会导致用户输入的内容不会和第二次数组变化的时候和对应的”名字”, “姓氏”对应上

结论 动态数组不要使用index作为key

key的选定要唯一并且恒定不变 <div key={Math.random()}></div>` 我今天出现的bug就是因为使用了会变化的key,就是使用了Math.random()

结论 千万不要使用Math.random()来生成key值

解决办法

    //当不能使用数组的index作为key 的时候,可以使用一个不变的全局变量来作为key
    let neverChangeNumber = 8;
    this.state.arr.forEach(function (item) {  
        item.id = neverChangeNumber++;
    })
    
    //当数组增加的时候:
    newItem.id =  neverChangeNumber++;
    const arr = this.state.arr;
    arr.push( newItem );

Key使用的注意事项

    //key不仅仅可以应用到动态数组创建组件上,其他组件也可以应用key.
    //例如: 
    this.state.isExchange ? 
    <div>
        <span>位置1</span>
        <span>位置2</span>
    </div>
    :
    <div>
        <span>位置2</span>
        <span>位置1</span>
    </div>

    //这个只是根据isExchange来判断时候交换位置
    //如果isExchange发生了变化会导致整个组件的销毁和重新创建
    //但是其实我们只是想交换两个span的位置
    //此时可以通过key来达到我们想要的效果,并且不会销毁组件

    this.state.isExchange ? 
    <div>
        <span key={1}>位置1</span>
        <span key={2}>位置2</span>
    </div>
    :
    <div>
        <span key={2}>位置2</span>
        <span key={1}>位置1</span>
    </div>

当isExchange发生变化时,

    <span key={1}>位置1</span>
    <span key={2}>位置2</span>

还没有重新创建, react只是将他们交换了位置,虽然这个时候看只是一个span, 但是如果这个span内部还有很多其他的结构,这样就减少很多的性能损耗.