vue中key使用的问题

vue,key,使用,问题 · 浏览次数 : 136

小编点评

**1.不使用key的时候vue是怎么处理的在vue2.x文档中有如下描述key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。** 当使用 `v-for` 循环遍历元素列表时,如果没有指定 `key` 属性,Vue 会使用 `index` 或 `item` 等元素的 `key` 属性值进行节点比较。如果所有元素都没有 `key` 属性,Vue 会使用 `oldVnode.key` 和 `vnode.key` 属性进行比较。 **2.使用key一定能提高diff效率么答案是并不是,可以看下面的例子** ```html
<div class="ttt" v-for="(item) in list" :key="item">{{item}}</div>
``` 在这个例子中,`item` 是一个字符串,如果使用 `key` 属性,由于 `item` 是字符串类型,Vue 会使用字符串的 `hash` 值作为 `key` 值进行节点比较。由于 `item` 的字符串 `'item'` 和 `'dog'` 的字符串 `'dog'` 相同,导致 `oldVnode.key` 和 `vnode.key` 相同,导致 diff 不进行。 **3.为什么说使用index作为key容易出错?** 使用 `index` 作为 `key` 的问题在于,`index` 是数字类型,而 `key` 属性应该是字符串类型。当使用 `index` 作为 `key` 时,Vue 会将数字类型的 `index` 转换为字符串类型,并进行字符串的比较。如果 `item` 的 `index` 是数字类型,而 `item` 的字符串 `'item'` 和 `'dog'` 的字符串 `'dog'` 相同,则也会导致 `oldVnode.key` 和 `vnode.key` 相同,导致 diff 不进行。 **4.使用key可以避免这个bug吗?** 是的,使用 `key` 可以避免在 `patch` 阶段认为两个元素是相同的 `VNode`,从而避免复用原来的 DOM 元素。

正文

前言

在vue要求在遍历的时候最好加上key,在使用过程中总有些疑问,在这里做下分析

1.不使用key的时候vue是怎么处理的

在vue2.x文档中有如下描述

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

在这段话中提到不使用key的时候,会尽量原地复用,复用的判断依据是常常在面试中被问到的sameVnode这个方法:

function sameVnode (a, b) {
  return (
    // 因为没有定义key,可以得知a.key和b.key都是undefined,进而后面的条件进行判断;
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

2.使用key一定能提高diff效率么

答案是并不是,可以看下面的例子

<script src="https://unpkg.com/vue@2.6.14/dist/vue.min.js"></script>
<div id="app">
  <div class="ttt" v-for="(item) in list">{{item}}</div>
  <button @click="changeData">更新</button>
</div>

<script >
  new Vue({
    el: '#app',
    data() {
      return {
        list: [1, 2, 3]
      }
    },
    mounted(){
      Array.from(document.querySelectorAll('.ttt')).forEach(item=>{
        item.dataset.mm = item.textContent;
      })
    },
    methods:{
      changeData(){
        this.list = [4, 5, 6]
      }
    }
  })
</script>

初始dom结构如下:

没有key情况下更新数组,然后DOM结构如下,可以看到dataset值没有变化,可以初步判断只是更新了div的内容

使用数组元素作为key情况下更新数组,然后DOM结构如下,可以看到dataset没有了,可以初步判断删除和创建了新的dom,很明显这样处理效率更低。

vue源码中的处理程序逻辑:
没有key的情况: updateChildren -> 判定为同类型节点(div),执行patchVnode方法 -> 再对div这个节点做updateChildren处理 -> 继续patchVnode,最后的结果就是更新文本节点内容;

if (oldVnode.text !== vnode.text) {
  nodeOps.setTextContent(elm, vnode.text)
}

有key并且key不相等: updateChildren -> key不同,判定不同类型节点(div),执行createElm方法,进而创建新的dom节点;

为什么说使用index作为key容易出错

demo查看

  • 复现步骤:在第一行的Input里输入1,在第二行Input里输入2,然后点第一行的“ד删除第一行
  • 期待结果:删除第一行后,应该变成“dog:2”
  • 实际结果:删除第一行后,变成了“dog:1”
<script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
<div id="app">
  <ul>
    <li v-for="(item, index) in list" :key="index">
      <test-row :name="item"></test-row>
      <span style="color: red;cursor: pointer" @click="handleRemove(index)">X</span>
    </li>
  </ul>
</div>

<script>
  Vue.component('test-row', {
    template: `<span>
      <span>{{ name }}:</span>
      <input v-model="nums"/>
      </span>`,
    props: {
      name: String
    },
    data() {
      return {
        nums: ''
      }
    }
  })

  new Vue({
    el: '#app',
    data() {
      return {
        list: ['cat', 'dog']
      }
    },
    methods: {
      handleRemove(i) {
        this.list.splice(i, 1);
      }
    }
  })
</script>

出现问题的原因:在patch阶段会认为这两个input子节点是sameVnode,进而复用原来的dom节点,因为组件只触发了更新,没有重新创建实例, 所以组件实例data数据没有变化,输入框内的内容就不会变化。

如果使用dog、cat作为key就可以避免这个bug;

与vue中key使用的问题相似的内容:

vue中key使用的问题

前言 在vue要求在遍历的时候最好加上key,在使用过程中总有些疑问,在这里做下分析 1.不使用key的时候vue是怎么处理的 在vue2.x文档中有如下描述 key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key

VUE中watch的详细使用教程

1、watch是什么? watch:是vue中常用的侦听器(监听器),用来监听数据的变化 2、watch的使用方式如下 watch: { 这里写你在data中定义的变量名或别处方法名: { handler(数据改变后新的值, 数据改变之前旧的值) { 这里写你拿到变化值后的逻辑 } } } 3、wa

vue 中安装并使用echart

本文为博主原创,转载请注明出处: 1.安装echart 依赖: 安装命令: npm install echarts --save 在vscode 的终端窗口进行执行,如图所示: 执行完之后,查看 项目中的echart 版本依赖是否添加成功: package-lock.json 中有具体的echart

Vue 中 Promise 的then方法异步使用及async/await 异步使用总结

转载请注明出处: 1.Promise 的 then 方法使用 then 方法是 Promise 中 处理的是异步调用,异步调用是非阻塞式的,在调用的时候并不知道它什么时候结束,也就不会等到他返回一个有效数据之后再进行下一步处理; 想了解 Promise 的使用,可以看这篇文章: ES6 中 Prom

在 Vue 中控制表单输入

Vue中v-model的思路很简单。定义一个可响应式的text(通常是一个ref),然后用v-model="text"将这个值绑定到一个input上。这就创造了一个双向的数据流: 用户在输入框中输入,text会发生变化。 text发生变化,输入框的值也随之变化。 让我们看看如何在Vue 3中使用v-

Vue 中为什么要有nextTick?

摘要:本文将浅析nextTick的作用、使用场景和背后的原理实现,希望对大家有所帮助。 本文分享自华为云社区《Vue 中的 nextTick 有什么作用?》,作者:CoderBin。 一、什么是nextTick 先看看官方对其的定义: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即

vue中新的状态管理器-pinia

背景 对于pinia的使用,可参考官方文档在这不做过多赘述。这边主要来讲讲pinia中 少用且好用的方法,为什么我们选择pinia而不用vuex ps: 以下写法全部基于组合式API 使用方式: 先下载依赖 npm i pinia -s 在vue3中,main.js这么写 import { crea

关于vue中image控件,onload事件里,event.target 为null的奇怪问题探讨

废话不多说(主要文笔比较差),直接上代码 一个简单的demo,如下 vue代码 imgLoaded(e) { deb

在Vue中使用Canvas绘制背景

好家伙, 在vue中使用canvas绘制与在html中使用canvas绘制大致相同, 但又有所区别 法一(无图片资源): vue中canvas的使用 - 掘金 (juejin.cn) 找到cancas元素; 创建context对象;getContext() 方法返回一个用于画布上绘图环境;参数 ‘2

Vue学习笔记(十):全局事件总线

之前博客中介绍了prop和调用事件的方式在父-子组件之间进行数据,这种方式在只有一层嵌套层时可以使用,但是路过存在多层嵌套,多层多个“兄弟”组件之间传递数据,就非常麻烦。对此,vue中提供了一种全局事件总线机制,数据传递是通过一个空的Vue实例作为中央事件总线,通过它来触发事件和监听事件,可以实现几...