Why do v-for variables need key value

First of all, we need to understand the Diffing algorithm

When comparing two trees, Vue first compares the root nodes of the two trees. Different types of root node elements have different shapes.

Compare elements of different types

When the root node is a different type of element, Vue will tear down the old tree and build a new one. For example, when an element changes from < a > to < img >, from < article > to < comment >, or from < button > to < div >, a complete reconstruction process will be triggered.

For example, when it is more variable than:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

Vue finds that the root node is different (< div > becomes < span >), discards all the old nodes, and then deletes and recreates them.

Compare elements of the same type

When comparing two elements of the same type, Vue retains the DOM node and only compares and updates the changed attributes. For example:

<div class="before" title="stuff" />

<div class="after" title="stuff" />

By comparing the two elements, Vue knows that only the class attribute on the DOM element needs to be modified.

Recursion of child nodes

By default, when recursing the child elements of a DOM node, Vue traverses the list of two child elements at the same time; when there is a difference, a mutation is generated to update.

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

Vue will compare the types of the two elements, and then judge whether the key value on the element is associated with the corresponding key value. Because there is no key added, there is no associated corresponding key, so there is no way to compare. By default, only the first element can be compared with the first one. The type of the element is the same, but the attributes of the element are different. textContent The text is different. All the text textContent in the updated element is' Connecticut '. Such violent update will cause DOM reflow, so it consumes performance very much.

To solve the above problems, Vue supports the key attribute. When a child element has a key, Vue uses the key to match the child elements on the original tree and the child elements on the latest tree. The following example makes the previous inefficient conversion efficient after adding a key:

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

Now Vue knows that only elements with '2014' key are new elements, and elements with '2015' and '2016' key are only moving.

And the key value is not recommended to use the index when traversing. If your list involves sorting or the list needs to add and delete options, unexpected problems will occur.

<template>
  <div class="hello">
    <ul>
      <li v-for="(item, index) in list" :key="index">
        <input type="checkbox">
        {{ item }}
      </li>
    </ul>
    <button @click="handleClick">Delete first</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data () {
    return {
      list: [1, 2, 3]
    }
  },
  methods: {
    handleClick () {
      this.list.shift()
    }
  }
}
</script>

When the first item is selected

Then click to delete the first item

The problem is that the second item is selected. The reason is that Vue reuses DOM

In the process of comparison, the key of the first item is the same so it is reused. If the attribute is changed differently, as long as the text in the node is the same, as long as the key of the second item is reused. If the attribute is changed differently, as long as the text in the node is the same, the key of the third item is not deleted, so actually clicking delete the button of the first item to delete the DOM content of the third item.

Then someone will ask why it can be reused if there is a difference between selecting and not selecting DOM, because the form data is processed by the DOM node (that is, within the DOM). Form data is not managed by Vue components (such as using v-model). So the properties on the two inputs are exactly the same, so the reuse of the first input is generated.

In the following cases, the deleted input will not be reused

<template>
  <div class="hello">
    <ul>
      <li v-for="(item, index) in list" :key="index">
        <input type="checkbox" v-model="item.check">
        {{ item.text }}
      </li>
    </ul>
    <button @click="handleClick">Delete first</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data () {
    return {
      list: [
        {text: 'a', check: false},
        {text: 'b', check: false},
        {text: 'c', check: false},
      ]
    }
  },
  methods: {
    handleClick () {
      this.list.shift()
    }
  }
}
</script>

So the key needs to use a unique ID

<template>
  <div class="hello">
    <ul>
      <li v-for="item in list" :key="item.id">
        <input type="checkbox">
        {{ item.text }}
      </li>
    </ul>
    <button @click="handleClick">Delete first</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  },
  data () {
    return {
      list: [
        {text: 'a', id: 1},
        {text: 'b', id: 2},
        {text: 'c', id: 3},
      ]
    }
  },
  methods: {
    handleClick () {
      this.list.shift()
    }
  }
}
</script>

In the diff process, first compare the first key. The key associated with the first key is not found, so delete the node. The second key exists, and the attributes are the same. Reuse the third key directly.

241 original articles published, praised 198, visited 200000+
Private letter follow

Tags: Vue Attribute

Posted on Sat, 07 Mar 2020 20:32:49 -0800 by Stu