在 Vue 的默认更新机制下,当一个组件重新渲染时,它的整个模板会重新执行,生成新的虚拟 DOM 树并与旧树对比。绝大多数情况下,这已经足够高效——但如果你明确知道某一部分内容永远不会再改变,或者只依赖于特定的几个数据,就可以用 v-once 和 v-memo 来跳过这些部分的重复渲染,进一步压榨性能。
v-once:只渲染一次,永不更新
v-once 是一个“一次性的”指令。它告诉 Vue:这个元素及其所有子节点,只在首次渲染时创建,后续任何数据变化都不要再动了。
典型场景:
- 展示一段后端返回的静态协议文本、公告内容、用户协议,这些内容一旦渲染就永远不会变。
- 列表的标题、表头等纯静态结构。
- 第三方富文本编辑器的预览区,内容只会在提交时一次性展示。
用法:
<div v-once>
<h1>{{ pageTitle }}</h1>
<p>{{ staticContent }}</p>
</div>
上述模板中,pageTitle 和 staticContent 的初始值会被渲染到 DOM 中,之后即使你修改了响应式数据 pageTitle 和 staticContent,这个 <div> 里的内容不会发生任何变化——Vue 会把它当作一个“静态块”,在后续的 Diff 中直接跳过。
内置的原理:v-once 会让编译器将该节点的 VNode 标记为 isOnce: true,并且在渲染时将虚拟节点缓存起来。后续组件更新进入 Diff 阶段时,当遇到标记为 isOnce 的节点,Vue 会直接复用旧的 VNode 和对应的真实 DOM,不触发任何对比。
注意事项:
- 只适用于真正永不变化的内容,如果里面的数据后续可能因为用户操作而改变,请勿使用
v-once。 - 过度使用意义不大,因为 Vue 的编译优化已经能够自动跳过纯静态内容。
v-once更多用于“内容本身依赖响应式数据,但业务上确定它不应该变”的场景。 v-once会锁定整个子树,即使子树里有自己的响应式状态(比如一个带有自己内部状态的子组件),也不会再更新。
v-memo:按条件缓存子树
v-memo 是 Vue 3.2+ 引入的更精细缓存指令。它接收一个依赖数组,当数组中的所有依赖值都没有变化时,该节点及其子树将完全跳过 Diff,直接复用上一次的渲染结果。
典型场景:
- 一个超长的列表项,只有当该列表项的数据本身变化时才需要更新,其他无关数据变化时,整个列表项应保持不变。
- 一个大组件中有多个相互独立的区域,每个区域只依赖于特定的几个状态,用
v-memo可以防止一个区域更新时连累另一个区域不必要的对比。
用法:
<div v-memo="[item.name, item.price]">
<span>{{ item.name }}</span>
<span>¥{{ item.price }}</span>
</div>
只有当 item.name 或 item.price 发生变化时,这个 <div> 内部才会重新渲染;如果只是 item.image 变了(不在依赖数组中),或者列表中的其他项变了,这个区域会直接使用缓存,零开销。
更强大的用法:结合 v-for
在 v-for 内部,v-memo 可以显著提升长列表的更新性能:
<div v-for="item in list" :key="item.id" v-memo="[item.selected]">
<span>{{ item.name }}</span>
<input type="checkbox" v-model="item.selected">
</div>
这里 v-memo 的依赖是 [item.selected]。当你勾选了某一项,导致该 item.selected 变化,只有这一项会更新,其余未选中的项不仅跳过 DOM 更新,连虚拟节点的对比都直接跳过。这比仅靠 key 的 Diff 优化更彻底,因为 key 只保证节点复用,仍然要进行属性对比。
原理:
在渲染时,Vue 会检查 v-memo 的依赖数组是否与上一次渲染时的值完全相同(浅比较)。如果相同,则直接返回缓存的 VNode,完全跳过该子树的新 VNode 创建和 Diff 过程。
注意事项:
- 依赖数组应尽可能精简且稳定,不要写
v-memo="[someObject]"(整个对象引用)除非对象引用真的不变,因为浅比较容易失效。 v-memo也应搭配key使用,在v-for中确保节点复用。- 和
v-once不同,v-memo是有条件缓存:当依赖变化时,它会正常更新,不会永久卡死。 - 不要滥用,只在确实有可测量的性能瓶颈时才使用。大多时候 Vue 自身的 Diff 优化已经足够,加入
v-memo反而增加心智负担。
如何选择
- 如果内容确定一次性生成且永不再变,用
v-once。 - 如果需要根据某几个特定条件决定是否跳过更新(尤其在长列表项中),用
v-memo。
两者都是“手动告诉编译器可以跳过某些工作”的指令,它们依赖开发者对业务逻辑的准确判断。用好它们,可以在极低开发成本下,把列表渲染、大量静态内容的更新性能推至极致。