Vue 的就地更新策略

145 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情

文章首发于语雀,如有问题欢迎评论指正,感谢!


Vue文档中关于v-for指南模块有这么一段话:

image.png

那什么是就地更新策略呢?我们先来写一个案例,通过案例来介绍。

const app = {
  template: `
    <ul>
    	<li v-for="(item, index) of list">
      	<span>{{ item.value }}</span>
        <button @click="delteItem(index)">Del</button>
      </li>
    </ul>`,
  data(){
    return{
      list: [
        {
          id: 1,
          value: "item-1"
        },
        {
          id: 2,
          value: "item-2"
        },
        {
          id: 3,
          value: "item-3"
        }
      ]
    }
  },
  methods: {
    delteItem(index) {
      this.list.splice(index, 1);
    }
  }
}

以上代码,实现了一个简单的列表,然后通过按钮可以进行删除某项,效果如下:

image.png

点击删除 DOM 的变化

当我们删除item-2的时候看看会发生什么?

屏幕录制2023-02-17 14.38.55.gif

从上面的动态图中我们可以看到:我们删除的是item-2li,第二个li里面的内容变成了item-3的内容,实际上删除的是第三个li(浏览器中紫色闪烁表示dom的内容发生了变化)。


这就是Vue的就地更新策略,回头看Vue文档的那句话:

image.png

Vue将使用一种最小化元素移动的算法,并尽可能地就地更新/复用相同类型的元素。也就是说默认的情况下Vue会尽量使用已经存在的DOM元素,直接在已有的DOM上进行复用修改,这样可以带来一定性能上的提升。


看一个更明显的案例:

const app = {
  template: `
  <div>
  	<div v-if="isLogin">
        <span>欢迎</span>
        <a href="javascript:;" @click="isLogin = false">xiechen</a>
    </div>
    <div v-else>
        <a href="javascript:;" @click="isLogin = true">登录</a>
        <a href="javascript:;">注册</a>
    </div>
  </div>`,
  data(){
    return{
      isLogin: false
    }
  }
}

以上代码,两个模块里都有a标签,按照我们的理解当条件发生变化的时候,div都会重新渲染,实际上Vue会进行就地更新策略。
屏幕录制2023-02-17 14.53.11.gif
可以很明显的看到第二个a标签一直在闪烁,也就是内容一直在更新而不是销毁重新创建的a标签!!!

就地更新的缺陷

再次回到Vue文档的那句话:
image.png
这又是什么意思呢?同样的我们还是用动态图要进行演示,我们把第一个案例进行改造,给每个li都新增一个input输入框:

<ul>
  <li v-for="(item, index) of list">
    <span>{{ item.value }}</span>
    <input type="text" />
    <button @click="delteItem(index)">Del</button>
  </li>
</ul>

屏幕录制2023-02-17 15.02.03.gif
实际的效果有点让人疑惑,我明明删除的是item-2,为什么item-3的输入框内容却发生了变化?

这其实就是「就地更新」的缺陷!!!
因为input输入框是一个临时的状态,Vue无法判断节点的value到底有什么用,所以在删除item2的时元素会进行复用,input里的值也是会被保留的,实际删除的是第三个li及第三个li内的input

如何进行解决?

现在的问题就是Vue不会根据最新的顺序去更新DOM,而是用已有的DOM进行属性的修改。

在这种情况下,我们需要给li标签绑定一个key属性,这样Vue就会根据最新的数据对DOM进行调整,而它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。

可以简单认为key是给每一个DOM节点一个唯一标识,这样Vue就不会启用就地更新了。

<ul>
  <li v-for="(item, index) of list" :key="item.id">
    <span>{{ item.value }}</span>
    <input type="text" />
    <button @click="delteItem(index)">Del</button>
  </li>
</ul>

屏幕录制2023-02-17 15.22.24.gif
从图片中可以看到,加上key属性后item-2删除后,item-2input也被删除啦。

key 的作用

简单说key的作用主要是为了更高效的对比虚拟DOM中每个节点是否是相同节点;

举个简单的例子:三胞胎战成一排,你怎么知道谁是老大?如果老大皮了一下子,和老三换了一下位置,你又如何区分出来?给他们挂个牌牌,写上老大、老二、老三。这样就不会认错了,key就是这个作用。

所以在使用key属性的时候需要注意,key属性必须是唯一的,不变的!当数据更新的时候,Vue可以通过key属性去确认元素,同时确认子元素是否要进行更新。

image.png