React中key的必要性与使用

作者: zenggo | 来源:发表于2017-03-07 11:08 被阅读6291次

React这个框架的核心思想是,将页面分割成一个个组件,一个组件还可能嵌套更小的组件,每个组件有自己的数据(属性/状态);当某个组件的数据发生变化时,更新该组件部分的视图。更新的过程是由数据驱动的,新的数据自该组件顶层向下流向子组件,每个组件调用自己的render方法得到新的视图,并与之前的视图作diff-比较差异,完成更新。这个过程就叫作reconciliation-调和

React通过virtual dom来实现高效的视图更新。基本原理是用纯js对象模拟dom树,每当更新时,根据组件们的render方法计算出新的虚拟dom树,并与此前的虚拟dom树作diff,得到一个patch(差异补丁),最后映射到真实dom树上完成视图更新。而两棵树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,很少出现跨越层级移动DOM元素的情况,所以React采用了简化的diff算法,只会对virtual dom中同一个层级的元素进行对比,这样算法复杂度就可以达到 O(n)。

由于React采用的diff算法是对新旧虚拟dom树同层级的元素挨个比较,碰到循环输出的元素时会有一些问题,比如列表。先来看一个例子:

// 旧v-dom
<ul>
  <li>first</li>
  <li>second</li>
</ul>
// 新v-dom
<ul>
  <li>zero</li>
  <li>first</li>
  <li>second</li>
</ul>

React在diff两棵树时,发现原来的两个li元素都与新v-dom中对应位置上的两个li元素不同,就会对其修改,并向真实dom树中插入新的second节点。实际上,我们可能只是进行了在first之前插入新zero节点的操作,而现在进行了额外的修改操作。
React官方文档提示我们应该使用key属性来解决上述问题。key是一个字符串,用来唯一标识同父同层级的兄弟元素。当React作diff时,只要子元素有key属性,便会去原v-dom树中相应位置(当前横向比较的层级)寻找是否有同key元素,比较它们是否完全相同,若是则复用该元素,免去不必要的操作。
延续第一个例子,如果每个li元素都有key属性:

// 旧v-dom
<ul>
  <li key="1">first</li>
  <li key="2">second</li>
</ul>
// 新v-dom
<ul>
  <li key="0">zero</li>
  <li key="1">first</li>
  <li key="2">second</li>
</ul>

现在React就知道了,新增了key为"0"的元素,而"1"与"2"仅仅移动了位置。
key必须是字符串类型,它的取值可以用数据对象的某个唯一属性,或是对数据进行hash来生成key。

<ul>
    {list.map(v=> <li key={v.idProp}>{v.text}</li>)}
</ul>

但是强烈不推荐用数组index来作为key。如果数据更新仅仅是数组重新排序或在其中间位置插入新元素,那么视图元素都将重新渲染。来看下例子:

<ul>{list.map((v,idx)=><li key={idx}>{v}</li>)}</ul>
// ['a','b','c']=>
<ul>
    <li key="0">a</li>
    <li key="1">b</li>
    <li key="2">c</li>
</ul>
// 数组重排 -> ['c','a','b'] =>
<ul>
    <li key="0">c</li>
    <li key="1">a</li>
    <li key="2">b</li>
</ul>

React发现key为0,1,2的元素的text都变了,将会修改三者的html,而不是移动它们。

渲染同类型元素不带key只会产生性能问题,如果渲染的是不同类型的状态性组件,组件将会被替换,状态丢失。

class Box extends React.Component {
    constructor(p) {
        super(p)
        this.state = {type: true}
        this.handler = this.handler.bind(this)
    }
    handler() {
        this.setState({
            type: !this.state.type
        })
    }
    render() {
        return (<div>
            <button onClick={this.handler}>haha</button>
            {this.state.type ? 
                (<div><Son_1 /><Son_2 /></div>)
                : (<div><Son_2 /><Son_1 /></div>)
            }
        </div>)
    }
}

如上述代码,每次按下按钮,原Son_1与Son_2组件的实例都将被销毁,并创建新的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>)
}

所以,碰到数组->列表的映射,或是同级元素需要移位的情况,一定要给元素加上key属性!

相关文章

  • React中key的必要性与使用

    React这个框架的核心思想是,将页面分割成一个个组件,一个组件还可能嵌套更小的组件,每个组件有自己的数据(属性/...

  • mysql中索引,联合索引及最左匹配

    Mysql中key 、primary key 、unique key 与index区别在平时,我们添加索引通常使用...

  • React 之key的使用

    key 我遇见的问题 在写项目的时候,项目中需要用到音频播放器,但是原始的 audio 标签在不同的浏览器,会有不...

  • Vue动态路由缓存不相互影响的解决办法

    关于react与vue中的key 之前在学习react的时候,常常遇到循环渲染组件时会提示需要在循环组件中加上ke...

  • key和数组方法

    key vue官网推荐我们使用v-for时,给每一个节点元素添加一个唯一的 key,其实不只是vue,react中...

  • React diff算法

    react/vue中,key的作用是什么? 为什么遍历时,key最好不要用index? 虚拟DOM中key的作用:...

  • 浅谈 React 列表渲染

    ? React 的列表渲染 key 与 Reconciliation List and Keys - ReactR...

  • vue和react中循环key的作用

    没用过react开发项目,但想来跟vue在循环渲染中key的作用应该原理是一样的。循环在没有使用key的时候,vu...

  • react navigation返回到任意页面(不集成redux

    react navigation默认是使用key作为goback的参数进行返回的,这个key是一个动态生成的,而不...

  • React中key的用处

    key的原理 React 元素可以具有一个特殊的属性 key,这个属性不是给用户自己用的,而是给 React 自己...

网友评论

  • wxainn:懂了,感谢
  • evelynlab:所以结论是,使用唯一id作为key比使用index 作为key性能更好?
    这么说来使用index作为key虽然不推荐,但是好像也不会有什么问题,对吗?
    还有,为什么移动节点比修改节点的html好呢?
    zenggo: @EvelynJS 是不会有什么问题,但是某些情况下影响性能。
    举个例子: 一个数组[a,b],渲染成两个不同类型的节点,key用index。然后数组变成[b,a],再次渲染时,react先判断变化前后同key的虚拟节点是否相等,发
    现节点类型就不同,接下来的操作就是新建两个dom节点去替换原来的节点。这种情况下key等于没用。而如果用唯一id,react会知道同key节点没有变化,只是换了位置,只要打个移动节点的patch到dom上,而不是新建替换。
    另外修改节点可能修改量无限大啊

本文标题:React中key的必要性与使用

本文链接:https://www.haomeiwen.com/subject/pwqpgttx.html