深刻浅出 Vue 中的 key 值

从前篇文章提及

前几天我写了一篇文章,sortable.js——Vue 数据更新问题 ,当时本身只是数据的强制刷新角度去分析,并且并没找到真正的“元凶”。javascript

很感谢有人帮我指出,多是 Vuekey 值,致使数据渲染不正确的。由此,我作了进一步的尝试。前端

key 的一个错误使用——使用 index 做为 key

不知道你在写 v-for 的时候,会不会直接使用 index 做为它的 key 值,是的,我认可我会,不得不说,这真的不是一个好习惯。vue

根据上篇文章,咱们仍是用 sortable.js 做为例子讨论。如下是核心代码,其中 arrData 的值为 [1,2,3,4]java

<div id="sort">
  <div v-for="(item,index) in arrData" :key="index" >
    <div>{{item}}</div>
  </div>
</div>
复制代码
mounted () {
    let el = document.getElementById('sort')
    var sortable = new Sortable(el, {
      onEnd: (e) => {
        const tempItem = this.arrData.splice(e.oldIndex, 1)[0]
        this.arrData.splice(e.newIndex, 0, tempItem)
      }
    })
  }
复制代码

固然一开始的时候,数据渲染确定是没有问题的node

好了,咱们来看下如下的操做: git

能够看到,我将3拖到2上面的时候,下面的数据变成了 1342,可是上面视图的仍是1234。而后我第四位置拖到第三位置的时候,下面的数据也是生效的,可是上面的数据彷佛所有错乱了。很好,咱们重现了案发现场。github

接着我改了绑定的 key 值,由于这里的例子比较特殊,咱们就认为 item 的值都不相同算法

<div id="sort">
  <div v-for="(item,index) in arrData" :key="item" >
    <div>{{item}}</div>
  </div>
</div>
复制代码

再看效果:数组

是的,这个时候数据就彻底跟视图同步了。bash

为何?

先看官方文档中 key 的一句介绍

有相同父元素的子元素必须有独特的 key。重复的 key 会形成渲染错误。

之因此会形成上面渲染错误的状况,是由于咱们的 key 值不是独特的,好比上面的 key 值,在调整数组顺序后就每一项原来的 key 值都变了,因此致使了渲染错误。

咱们先来得出一个结论,index 做为 key 值是有隐患的,除非你能保证 index 始终可以可以做为一个惟一的标识

key 值到底有什么用

vue2.0 以后,咱们不写 key 的话,就会报 warning,那也就是说官方是但愿咱们写 key 值的,那么 key 到底在 vue 中扮演了什么样的角色?

不使用 key 能够提升性能么 答案是,是的!能够!

先看官方解释:

若是不使用 key,Vue 会使用一种最大限度减小动态元素而且尽量的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化从新排列元素顺序,而且会移除 key 不存在的元素。

好比如今有一个数组 [1,2,3,4]变成了[2,1,3,4],那么没有 key 的值会采起一种“就地更新策略”,见下图。它不会移动元素节点的位置,而是直接修改元素自己,这样就节省了一部分性能

而对于有 key 值的元素,它的更新方式以下图所示。能够看到,这里它对 DOM 是移除/添加的操做,这是比较耗性能的。

居然不带 key 性能更优,为什么还要带 key 先来看一个例子,核心代码以下,这里模仿一个切换 tab 的功能,也就是切换的tab1 是1,2,3,4。tab2 是 5,6,7,8。其中有设置了一个点击设置第一项字体色为红色的功能。

那么当咱们点击tab1将字体色设置成红色以后,再切换到 tab2,咱们预期的结果是咱们第一项字体的初始颜色而不是红色,可是结果却仍是红色。

<div id="sort">
  <button @click="trunToTab1">tab1</button>
  <button @click="trunToTab2">tab2</button>
  <div v-for="(item, index) in arrData">
    <div @click="clickItem(index)" class="item">{{item}}</div>
  </div>
</div>
复制代码
trunToTab1 () {
        this.arrData = [1,2,3,4]
      },
      trunToTab2 () {
        this.arrData = [5,6,7,8]
      },
      clickItem () {
        document.getElementsByClassName('item')[0].style.color = 'red'
      }
复制代码

这就超出了咱们的预期了,也就是官方文档所说的,默认模式指的就是不带 key 的状态,对于依赖于子组件状态或者临时 DOM 状态的,这种模式是不适用的。

这个默认的模式是高效的,可是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出。

咱们来看带上 key 以后的效果

这就是官方文档之因此推荐咱们写 key 的缘由,根据文档的介绍,以下:

使用 key,它会基于 key 的变化从新排列元素顺序,而且会移除 key 不存在的元素。 它也能够用于强制替换元素/组件而不是重复使用它。当你遇到以下场景的时候它可能会颇有用:

  • 完整地触发组件的生命周期钩子
  • 触发过渡

那么 Vue 底层 key 值究竟是怎么去作到以上的功能?咱们就得聊聊 diff 算法以及虚拟 DOM 了。

key 在 diff 算法中的做用

这里咱们不谈 diff 算法的具体,只看 key 值在其中的做用。(diff 算法有机会咱们再聊)

vue 源码中 src/core/vdom/patch.js

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
复制代码

咱们整理一下代码块:

// 若是有带 key
  if (isUndef(oldKeyToIdx)) {
    // 建立 index 表
    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
  }
  if (isDef(newStartVnode.key)) {
    // 有 key ,直接从上面建立中获取
    idxInOld = oldKeyToIdx[newStartVnode.key]
  } else {
    // 没有key, 调用 findIdxInOld
    idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
  }
复制代码

那么最主要仍是 createKeyToOldIdxfindIdxInOld 两个函数的比较,那么他们作了什么呢?

function createKeyToOldIdx (children, beginIdx, endIdx) {
  let i, key
  const map = {}
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key
    if (isDef(key)) map[key] = i
  }
  return map
}
复制代码
function findIdxInOld (node, oldCh, start, end) {
    for (let i = start; i < end; i++) {
      const c = oldCh[i]
      if (isDef(c) && sameVnode(node, c)) return i
    }
  }
复制代码

咱们能够看到,若是咱们有 key 值,咱们就能够直接在 createKeyToOldIdx 方法中建立的 map 对象中根据咱们的 key 值,直接找到相应的值。没有 key 值,则须要遍历才能拿到。相比于遍历,映射的速度会更快。

key 值是每个 vnode 的惟一标识,依靠 key,咱们能够更快的拿到 oldVnode 中相对应的节点。

参考

第 1 题:写 React / Vue 项目时为何要在列表组件中写 key,其做用是什么?

解析vue2.0的diff算法

欢迎你们关注个人前端大杂货铺~

欢迎你们关注个人前端大杂货铺~
相关文章
相关标签/搜索