学完这篇你能拿到 10 个可以直接照做的 Vue 3 性能优化手段,每条都给出具体写法、参数和适用场景,改完能明显感觉到页面更快、包更小。下面按“先定位、再改代码、最后管包体”的顺序来讲。
第一步:先用工具定位瓶颈,别凭感觉优化
打开浏览器 DevTools 的 Performance 面板录一段交互,看是脚本执行久、渲染久还是布局抖动。Vue 项目还可以装 vite-plugin-vue-devtools,在开发时看组件渲染耗时和更新次数。生产环境用 npm run build 后跑一次 npx vite preview,用 Lighthouse 看 LCP、TBT 指标,作为优化前后的对比基线。
注意:不要一边开着 Vue Devtools 扩展一边测生产性能,扩展本身会显著拖慢渲染。
第二步:用 v-memo 和 v-once 跳过无意义重渲染
长列表里,单个条目的数据没变,但父组件更新时它还是会重渲染。给 v-for 的项加 v-memo,只在依赖变化时才更新:
<div v-for="item in list" :key="item.id" v-memo="[item.id, item.selected]">
{{ item.name }}
</div>
静态区块用 v-once 标记,只渲染一次:
<footer v-once>{{ copyright }}</footer>
注意:v-memo 数组里漏写依赖会导致该更新的不更新,只把真正会影响这块 DOM 的变量写进去。
第三步:计算属性优先,watch 里别做重活
能由已有状态推导出来的值,一律用 computed(有缓存),不要写成方法调用或者 watch 里手动赋值。watch 只留给副作用,比如请求接口、操作 DOM。需要深度监听大对象时改用 watch(() => obj.id, ...) 这种精确来源,避免 deep 遍历。
第四步:大列表上虚拟滚动
列表超过 300 条就考虑虚拟滚动,只渲染可视区域的节点。可以直接引 vue-virtual-scroller 或 @tanstack/vue-virtual:
npm i @tanstack/vue-virtual
配合固定行高使用,容器必须有明确高度,否则算不出可视区。分页也是同样有效的方案,别把几千条数据一次性塞进 DOM。
第五步:用 shallowRef 处理大对象
一个几万条记录的数组或大型图表配置,用 ref 会做深度响应式代理,非常伤性能。这种“整体替换、不关心内部字段变化”的数据改用:
import { shallowRef, triggerRef } from 'vue'
const bigData = shallowRef([])
bigData.value = newList // 整体替换,正常触发更新
bigData.value.push(x); triggerRef(bigData) // 原地改需手动触发
第六步:拆分组件,让更新粒度变细
一个巨型组件里任何一个响应式变量变化都会触发整个组件重新渲染。把列表项、弹窗、表单拆成子组件,配合 props 传入,Vue 就只重渲染受影响的那一小块。拆完后用 Devtools 的 “Highlight updates” 验证是否真的只更新了局部。
第七步:路由和重组件懒加载
路由全部改成动态导入:
const routes = [{ path: '/admin', component: () => import('@/views/Admin.vue') }]
页面里的重型组件(富文本编辑器、图表、地图)也用 defineAsyncComponent 异步加载,并给个骨架屏:
const Editor = defineAsyncComponent(() => import('@/components/RichEditor.vue'))
第八步:把第三方库按需引入
Element Plus、Ant Design Vue、lodash 这类库最容易拖大包体。UI 库用官方按需引入插件(如 unplugin-vue-components + unplugin-auto-import),lodash 改成 import debounce from 'lodash/debounce' 而不是整体导入。日期库优先选 dayjs 而不是 moment。
注意:改完务必跑一次 npx vite build --report 或装 rollup-plugin-visualizer 看包体分析图,确认依赖真的被摇掉了。
第九步:v-if 与 v-show 用对,key 别乱写
频繁切换显示用 v-show(只切 display),条件很少变用 v-if(不渲染就不占内存)。v-for 的 key 一定用业务唯一 id,不要用数组下标——用下标在插入、删除时会导致大量节点被错误复用,性能和数据都会出问题。
第十步:开启构建层优化
在 vite.config.js 里做几件事:生产构建默认已开启压缩,可再加 gzip/brotli 预压缩插件;把 vue、vue-router、pinia 等稳定依赖配到 build.rollupOptions.output.manualChunks 拆出 vendor 包,利用浏览器长缓存;图片资源走 CDN 或转为 webp。
build: {
rollupOptions: {
output: {
manualChunks: { vendor: ['vue', 'vue-router', 'pinia'] }
}
}
}
小结
- 先测后改:用 Performance 面板和 Lighthouse 建立前后对比基线。
- 减少渲染:
v-memo、v-once、拆分组件、正确的 key,四件事收益最大。
- 大对象用
shallowRef,大列表用虚拟滚动。
- 减小包体:路由懒加载、重型组件异步、第三方库按需引入、vendor 分包。
- 每次改完都跑一次构建分析,确认优化真实生效而不是心理安慰。