From the previous article

A few days ago, I wrote an article, sortable.js — Vue data update problem. At that time, I only analyzed data from the perspective of forced refresh, and did not find the real “culprit”.

Thank you for pointing out to me that it may be the key value of Vue that causes the data rendering to be incorrect. So I took it a step further.

An incorrect use of key — useindexAs akey

I don’t know if you would use index as the key when you write v-for, but yes, I do, and I have to say, that’s not a good habit.

Based on the previous article, we will use sortable.js as an example for discussion. Here is the core code, where arrData is [1,2,3,4]

<div id="sort">
  <div v-for="(item,index) in arrData" :key="index" >
    <div>{{item}}</div>
  </div>
</div>
Copy the code
  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)
      }
    })
  }
Copy the code

Of course, in the beginning, data rendering is no problem

Ok, let’s look at the following operations:

As you can see, when I drag 3 over 2, the bottom number becomes 1342, but the top view is still 1234. Then when I drag the fourth position to the third position, the following data is also valid, but the above data seems to be all confused. Good. We reconstructed the crime scene.

And then I change the key of the binding, because this is a special case, and we assume that the value of item is different

<div id="sort">
  <div v-for="(item,index) in arrData" :key="item" >
    <div>{{item}}</div>
  </div>
</div>
Copy the code

Look again at the effect:

Yes, at this point the data is completely synchronized with the view.

Why is that?

Let’s take a look at key in the official document

Child elements that have the same parent must have a unique key. Duplicate keys cause render errors.

The reason for the above rendering error is that our key value is not unique. For example, the above key value changed the original key value of each item after adjusting the array order, resulting in the rendering error.

Let’s start with the conclusion that using index as a key is dangerous unless you can guarantee that index will always be a unique identifier

What does a key value do anyway

After VUE 2.0, if we do not write a key, we will report a warning. That is, we are expected to write a key.

Can you improve performance without keys the answer is yes! You can!

First, the official explanation:

If keys are not used, Vue uses an algorithm that minimizes dynamic elements and tries to repair/reuse the same type of elements as much as possible. With a key, it rearranges elements based on the change in the key and removes elements where the key does not exist.

For example, if an array [1,2,3,4] becomes [2,1,3,4], values without keys will adopt an “in-place update strategy”, as shown in the figure below. Instead of moving the element node, it directly modifies the element itself, which saves some performance

For an element with a key value, it is updated as shown in the figure below. As you can see, here it is a remove/add operation to the DOM, which is very performance intensive.

Tab1 (1,2,3,4); tab1 (1,2,3,4); Tab2,6,7,8 five. There is a function to click and set the first font color to red.

So when we click on Tab1 to set the font color to red and switch to Tab2, we expect the original color of our first font instead of red, but the result is still red.

<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>
Copy the code
      trunToTab1() {this.arrdata = [1,2,3,4]},trunToTab2() {this.arrdata = [5,6,7,8]},clickItem () {
        document.getElementsByClassName('item')[0].style.color = 'red'
      }
Copy the code

This is unexpected, as the official documentation states that the default mode refers to keyless state and is not suitable for dependencies on child component state or temporary DOM state.

This default mode is efficient, but only for list rendering output that does not depend on child component state or temporary DOM state (for example, form input values).

So let’s see what happens when we add a key

This is the reason why the official document recommends us to write key. According to the introduction of the document, it is as follows:

With a key, it rearranges elements based on the change in the key and removes elements where the key does not exist. It can also be used to force elements/components to be replaced rather than reused. It can be useful when you encounter situations like:

  • Triggers the component’s lifecycle hook completely
  • Trigger a transition

So how does Vue bottom key value achieve the above function? We need to talk about diff algorithms and the virtual DOM.

The role of key in diff algorithm

We’re not going to talk about the diff algorithm, we’re just going to look at the key. (Diff algorithm, we’ll talk about it later.)

See SRC /core/vdom/patch.js in vue source code

if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
Copy the code

Let’s tidy up the code block:

  // If there is a key
  if (isUndef(oldKeyToIdx)) {
    // Create index table
    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
  }
  if (isDef(newStartVnode.key)) {
    // There is a key, obtained directly from the above creation
    idxInOld = oldKeyToIdx[newStartVnode.key]
  } else {
    // No key, call findIdxInOld
    idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
  }
Copy the code

So the main thing is to compare createKeyToOldIdx and findIdxInOld, so what do they do?

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
}
Copy the code
 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
    }
  }
Copy the code

We can see that if we have a key value, we can directly find the corresponding value based on our key in the map object created in the createKeyToOldIdx method. If there is no key value, you need to iterate to get it. The mapping is faster than traversal.

keyThe value is each of themvnodeThe unique identifier of the dependencykeyWe can get it fasteroldVnodeThe corresponding node in.

reference

React/Vue: Why do you write a key in a list component when writing React/Vue?

Analyze the diff algorithm of vue2.0

Welcome everyone to pay attention to my front end big grocery store ~