Vue3中的v-memo

avatar
前端工程师 @天鹅到家

image.png

前言

这两天在阅读Vue3官方文档时,发现在Vue3.2版本中,新增了一个名为v-memo的指令,这让我想到了react中的useMemo。在仔细阅读了文档描述后发现,这果然是一个用于页面性能优化的指令。

什么是v-memo?

官方文档中是这样描述该指令的:

缓存一个模板的子树。在元素和组件上都可以使用。为了实现缓存,该指令需要传入一个固定长度的依赖值数组进行比较。如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过.

举个例子:

<div v-memo="[valueA, valueB]">
  ...
</div>

当组件重新渲染,如果 valueA 和 valueB 都保持不变,这个 <div> 及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。 正确指定缓存数组很重要,否则应该生效的更新可能被跳过。v-memo 传入空依赖数组 (v-memo="[]") 将与 v-once 效果相同。

简单来说,v-memo 接受一个依赖的数组,依赖的数组变化,v-memo 所对应的 DOM 包括子集将会重新渲染,反过来说,如果依赖的数组不变,即使整组件重新渲染了,v-memo 所对应的 DOM 包括子集更新都将被跳过。 如果用一个空数组调用 v-memo 相当于使用 v-once,只会渲染该部分组件一次。

可以说,v-memo相当于一个作用对象是DOM的计算属性。

使用

一、手动控制节点更新

通过使用v-memo,可以手动控制节点是否更新

下面这个例子中有三个计数器,但只有在被依赖的 Subscribers 变化时, 子节点才会重新渲染。

<script setup>
import { ref } from 'vue'

const subscribers = ref(4000)
const views = ref(10000)
const likes = ref(3000)
</script>
<template>
  <div>
    <div v-memo="[subscribers]">
      <p>Subscribers: {{ subscribers }}</p>
      <p>Views: {{ views }}</p>
      <p>Likes: {{ likes }}</p>
    </div>
    <button @click="subscribers++">Subscribers++</button>
    <button @click="views++">Views++</button>
    <button @click="likes++">Likes++</button>
    <div>
      <p>Current state:</p>
      <p>Subscribers: {{ subscribers }}</p>
      <p>Views: {{ views }}</p>
      <p>Likes: {{ likes }}</p>
    </div>
  </div>
</template>
  

在修改子节点依赖的views,likes时,子节点 p 将不会重新渲染,但只要更新subscribers,div 中的内容即会重新渲染。

improve-vue-performance-with-v-once-and-v-memo-image-3.gif

二、与v-for配合使用

v-memo常用于需要渲染大量v-for列表的时候

下面用一个例子进行演示:

<script setup>
import { ref } from 'vue'
   // 被选中的值
  const selected = ref(0);
  // 造的列表数据
  const list = ref(
    Array.from({ length: 1001 }, (_, index) => {
      return {
        id: index,
        name: `test${index}`,
      };
    }),
  );
  // 选中点击方法
  const onClickSelect = (id) => {
    selected.value = id;
    // 记录开始时间
    console.time()
    nextTick(()=> {
      // 记录结束时间
      console.timeEnd()
    })
  }
</script>
<template>
  <div>
    <div v-for="item in list" @click="onClickSelect(item.id)" v-memo="[item.id == selected]">
      <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
    </div>
  </div>
</template>

当组件的 selected 状态改变,默认会重新创建大量的 vnode,尽管绝大部分都跟之前是一模一样的。

在点击 v-for 生成列表中任意一项时,selected值会发生改变。

image.png

在不使用 v-memo时,可以看到,控制台输出的渲染时间为

image.png 在添加上v-memo后,渲染时间为

image.png 可以看出,有了一些较小的性能提升

再将数据增加到一万条时,不使用 v-memo的平均 DOM更新时间大约为 55ms+

image.png

使用 v-memo的平均 DOM更新时间大约为 30ms+

image.png

总结

可以看到,v-memo在数据较多的情况下,是可以通过手动操作对页面进行优化的,但使用场景大多在中型或大型项目中。

通过以上例子,也正验证了Vue官方文档中对该指令的描述:用于性能至上场景中的微小优化。

该文章借鉴了这位大佬的文章