前言

Vue3 本身已经很快了——Proxy 响应式、PatchFlag 编译优化、Tree Shaking……

但"框架快"不等于"你的项目快"。

在实际项目中,性能问题往往来自:

  • ❌ 超大列表一次性渲染几千条

  • ❌ 频繁更新的响应式数据触发了大量重新渲染

  • ❌ 不必要的深层响应式代理拖慢初始化

  • ❌ 图片和资源没有懒加载

这一篇,我们系统梳理 9 个 Vue3 性能优化的实战策略,从响应式层面 → 渲染层面 → 资源层面,层层递进。


一、响应式层面优化

策略 1:shallowRef / shallowReactive —— 跳过深层代理

问题场景
// 一个巨大的列表,不需要深层响应式
const bigList = reactive(largeData) // ❌ 每一项都被代理
优化方案
import { shallowRef } from 'vue'

// 只有 .value 本身是响应式的,内部数据不被代理
const bigList = shallowRef(largeData)

// 需要更新时,整体替换
bigList.value = newData

API

响应式深度

适用场景

ref/ reactive

深层

需要细粒度更新的数据

shallowRef

仅顶层

大列表、不可变数据

shallowReactive

仅第一层

配置对象、只读结构

📌 经验法则

如果数据不需要逐项修改,就用 shallow 版本。


策略 2:markRaw —— 标记永不代理

import { markRaw } from 'vue'

const staticConfig = markRaw({
  theme: 'dark',
  version: '1.0.0'
})

// 即使放进 reactive,也不会被代理
const state = reactive({
  config: staticConfig // ✅ 不会被代理
})

适合:第三方库实例、常量配置、大型静态数据


策略 3:toRefs 的替代方案 —— 只暴露需要的响应式属性

// ❌ 把整个 reactive 解构暴露
const state = reactive({ a: 1, b: 2, c: 3 })
return { ...toRefs(state) } // 全部变成 ref

// ✅ 只暴露需要的
return {
  a: computed(() => state.a),
  b: computed(() => state.b)
}

二、渲染层面优化

策略 4:v-memo —— 条件性跳过 Diff

<template>
  <div v-memo="[dep1, dep2]">
    <!-- 只有当 dep1 或 dep2 变化时才重新渲染 -->
    <ExpensiveComponent :data="data" />
  </div>
</template>

📌 与 computed 的区别

  • computed缓存计算结果

  • v-memo缓存整个子树的渲染

⚠️ 不要滥用:只在确实昂贵的子树中使用


策略 5:虚拟列表(Virtual List)—— 长列表终极方案

问题
<!-- 渲染 10000 条数据 -->
<div v-for="item in 10000" :key="item">
  {{ item }}
</div>

❌ DOM 节点过多,页面卡死


解决方案:vue-virtual-scroller / vueuc
<template>
  <RecycleScroller
    class="scroller"
    :items="largeList"
    :item-size="50"
    key-field="id"
    v-slot="{ item }"
  >
    <div class="row">
      {{ item.name }}
    </div>
  </RecycleScroller>
</template>

<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
</script>

只渲染可视区域内的元素,DOM 数量恒定


策略 6:v-for 与 v-if 的正确用法

<!-- ❌ 错误:v-if 和 v-for 在同一元素 -->
<div v-for="item in list" v-if="item.active" :key="item.id">
  {{ item.name }}
</div>
<!-- ✅ 正确:先过滤,再循环 -->
<div v-for="item in activeList" :key="item.id">
  {{ item.name }}
</div>
const activeList = computed(() =>
  list.value.filter(item => item.active)
)

📌 Vue3 中 v-if 优先级高于 v-for,但混用仍然不推荐。


三、组件层面优化

策略 7:组件懒加载(defineAsyncComponent)

import { defineAsyncComponent } from 'vue'

// 路由级组件
const UserDetail = defineAsyncComponent(() =>
  import('@/views/UserDetail.vue')
)

// 条件渲染的重型组件
const RichEditor = defineAsyncComponent(() =>
  import('@/components/RichEditor.vue')
)
<template>
  <button @click="showEditor = true">打开编辑器</button>
  <RichEditor v-if="showEditor" />
</template>

只有需要时才会加载组件代码


策略 8:KeepAlive 缓存 + max 控制

<KeepAlive :max="10">
  <component :is="currentTab" />
</KeepAlive>

✅ 前面第 16 篇详细讲过,这里不再赘述


四、资源层面优化

策略 9:图片懒加载

方案一:原生 loading 属性(最简单)
<img src="large-image.jpg" loading="lazy" alt="描述" />

✅ 浏览器原生支持,零依赖


方案二:Intersection Observer 自定义指令
// directives/lazy.ts
export const lazy = {
  mounted(el: HTMLImageElement, binding: any) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          el.src = binding.value
          observer.unobserve(el)
        }
      })
    })
    observer.observe(el)
  }
}
<template>
  <img v-lazy="'/images/photo.jpg'" alt="照片" />
</template>

五、性能优化 Checklist

类别

检查项

响应式

大对象用 shallowRef/ markRaw

渲染

长列表用虚拟列表

渲染

昂贵子树用 v-memo

组件

重型组件用 defineAsyncComponent

资源

图片懒加载

构建

路由懒加载

构建

开启 Gzip / Brotli

监控

使用 Vue DevTools Profiler


六、性能分析工具

1️⃣ Vue DevTools Performance Tab

  • 记录组件渲染时间

  • 找出渲染瓶颈


2️⃣ Chrome DevTools Performance

  • 录制页面操作

  • 分析长任务和强制回流


3️⃣ Lighthouse

npm install -g lighthouse
lighthouse http://localhost:5173

七、常见优化误区

误区

真相

所有数据都用 shallowRef

过度优化,增加维护成本

v-memo 到处用

反而增加内存开销

虚拟列表用于短列表

杀鸡用牛刀

过早优化

先测量,再优化

📌 Donald Knuth 的名言

"过早优化是万恶之源。"


八、面试高频问答

Q1:shallowRef 和 ref 的区别?

shallowRef 只有 .value 是响应式的,内部数据不被代理。

Q2:v-memo 的使用场景?

渲染开销大的子树,且依赖变化不频繁的场景。

Q3:长列表优化的核心思路?

只渲染可视区域内的元素(虚拟列表)。


九、总结(实战版)

Vue3 性能优化不是"玄学",而是分层递进的系统工程

  1. 响应式层:减少不必要的代理

  2. 渲染层:减少 DOM 数量和 Diff 开销

  3. 组件层:延迟加载和缓存

  4. 资源层:懒加载和压缩


📢 下期预告

👉 第 22 篇:渲染优化 —— PatchFlags 与 Tree Shaking 的黑盒解析

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐