Skip to content

05|v-once 与 v-memo ​

第七章 / 05。目标:把“跳过更新”写成可验证的语义合同,而不是凭感觉贴优化指令。

两种跳过条件 ​

  • v-once:首次渲染后,整个子树以后都当作静态内容。它仍会读取第一次的运行时值,但后续值变化不会反映到 DOM。
  • v-memo="[a, b]":比较本次与上次依赖数组;每一项按 Object.is 比较均未变时,跳过该子树的 VNode 创建和更新。

普通静态 HTML 已由编译器自动优化。v-once 用于“值来自运行时,但合同规定首次值永久展示”;v-memo 只用于已测得的昂贵大子树或大列表微优化。

可重放实验:故意漏掉依赖 ​

把完整组件放入第一章 Vite 项目的 src/App.vue,每轮实验刷新恢复初始状态。

vue
<script setup lang="ts">
import { ref } from 'vue'

const build = ref('2026.09')
const selectedId = ref('a')
const locale = ref<'zh' | 'en'>('zh')
const rows = [{ id: 'a', zh: '甲', en: 'A' }, { id: 'b', zh: '乙', en: 'B' }]
</script>

<template>
  <p v-once data-test="build">首次构建号:{{ build }}</p>
  <button @click="build = '2026.10'">改构建号</button>
  <button @click="selectedId = selectedId === 'a' ? 'b' : 'a'">改选中项</button>
  <button @click="locale = locale === 'zh' ? 'en' : 'zh'">改语言</button>

  <ul>
    <li
      v-for="row in rows"
      :key="row.id"
      v-memo="[row.id === selectedId]"
      :aria-current="row.id === selectedId ? 'true' : undefined"
    >
      {{ locale === 'zh' ? row.zh : row.en }}
    </li>
  </ul>
</template>

构建号状态会变但 DOM 保持首次值,这是 v-once 的合同。切换语言时,列表文本可能不更新:memo 数组遗漏了 locale。修成 v-memo="[row.id === selectedId, locale]" 后才满足当前模板的依赖合同。

对 v-for 使用 v-memo 时应与 v-for 放在同一元素上;Vue 会从 item 的 key 推断身份,不需要把 row.id 再塞入 memo 数组。若数组元素每次都是新对象,引用身份总是变化,也不会命中跳过。

一次漏依赖为什么会被另一次更新掩盖 ​

初始时 a 行依赖是 [true],b 行是 [false]。只把 locale 从 zh 改成 en,两行的依赖数组都未变化,所以渲染器复用旧子树,文字仍是甲、乙。随后把选中项从 a 改成 b,两行依赖分别变为 [false]、[true],这次都重新创建子树,才读到当前的 en,显示 A、B。于是“多点几次就好了”并不证明依赖完整。

增加 dense 后同理:只修改密度,应该立即更新 class,不应等另一次选中变化。要找全依赖,需要列出子树实际会变化的输出:选中属性、语言文字、密度 class,再分别让每个输入单独变化。key 解决这一行是谁,memo 解决这一行这次是否有必要重新计算;二者不能互相替代。

先证明行为等价,再比较成本 ​

  1. 移除 v-memo,锁定正确的文本、选中状态、焦点和事件行为。
  2. 用同一数据规模和同一操作录制基线,记录组件更新时间线或自定义 mark。
  3. 添加完整依赖,再跑相同行为断言。
  4. 比较多轮数据的分布和环境,不承诺固定倍数;若差异落在噪声内,删除优化。

练习:找出缺失依赖 ​

给上面的列表再增加 dense: boolean,它控制每行 class。初始 memo 仍只有选中状态。先手动切换并记录实际文本/class;完成 测试环境 后回访,写出会失败的自动化断言,分别证明改变 locale 和 dense 时 DOM 变陈旧;然后补全最小依赖数组,并证明只切换选中项时未受影响。答案必须解释为何不把 row 整个对象放入数组。

参考答案:第 7 章第 05 题。

用心记录每一步成长