🔥这 10 个 Vue3 性能优化技巧,藏太深了,建议保存!

说实话,Vue3 出来这么久,光 Composition API 和响应式就够大家研究半天了。但性能优化这块,很多人还停留在"用 v-if 替代 v-show""不要重复渲染"这种 level。

今天我把这段时间优化项目中踩过的坑、用过的 trick,总结成 10 条 Vue3 性能优化技巧,有些细节藏得太深,建议你直接收藏起来,项目上线前一条条过一遍。


1️⃣ defineComponent 别乱用,能用 SFC 就别函数式

Vue3 支持函数式组件,但在大量场景中,过度使用 defineComponent() + setup() 可能引入不必要的闭包和响应式计算,影响初始化性能。

建议 :通用组件写 SFC(单文件组件)+ <script setup>,比闭包组件更易优化、缓存、调试。


2️⃣ watchEffect 是利器也是地雷,别用在 layout 逻辑上

很多人为了方便直接把 DOM 操作放在 watchEffect 里。结果组件一更新、依赖变动,就疯狂触发。

ts 复制代码
watchEffect(() => {
  el.value.scrollTop = 0 // ❌:这是副作用,不应该放在响应式追踪中
})

正确做法 :用 watch(),带 debounce,控制副作用频率。


3️⃣ v-for 列表的 key 千万别写错:index 是性能杀手

这一条大家可能听过,但还是会踩。用 index 做 key,在动态更新时 Vue 无法复用 DOM,直接销毁重建。

html 复制代码
<!-- ❌ -->
<div v-for="(item, index) in list" :key="index">{{ item.name }}</div>

<!-- ✅ -->
<div v-for="item in list" :key="item.id">{{ item.name }}</div>

尤其是在频繁更新列表的数据可视化、表格组件中,key 写错会直接触发 DOM 回流重排


4️⃣ 慎用 computed 返回复杂对象,响应式追踪炸裂

你写一个 computed(() => complexObj),Vue 会追踪整个对象。如果里面有大量字段、方法,性能直接起飞。

ts 复制代码
// ❌:容易让响应式系统过度追踪
const userMap = computed(() => new Map(users.value.map(u => [u.id, u])))

// ✅:改成 flat array,或者只计算需要的字段

5️⃣ 不要把 ref([]) 和大对象直接暴露给多个组件使用

误用场景:你写了一个 ref([]),然后传给多个子组件共享。结果改了一项,整个数组被追踪一次,导致每个使用者都被动更新。

解决方法

  • shallowRef() 包一层,手动控制更新
  • 或者用 Proxy + 自定义 getter 精确追踪字段

6️⃣ 大量动态组件不要用 <component :is="xxx" />,性能崩溃

动态组件虽然香,但会带来:

  • 挂载/卸载频繁
  • 缓存无效
  • 状态丢失

建议替代方案

html 复制代码
<keep-alive>
  <MyTabA v-if="tab === 'a'" />
  <MyTabB v-else-if="tab === 'b'" />
</keep-alive>

显式声明组件 + keep-alive,提升切换性能。


7️⃣ 自定义指令别滥用,mounted 中 DOM 读写容易造成布局抖动

比如做 tooltip、position 定位的指令,如果在 mounted() 中读取 getBoundingClientRect(),会触发强制重排。

解决方式

ts 复制代码
requestAnimationFrame(() => {
  // 延迟读取,防止抖动
})

8️⃣ 小心 v-model@update:modelValue 的双向绑定递归问题

Vue3 虽然简化了 v-model,但双向绑定 + 父组件 watch 一不小心就会触发递归。

实战建议

ts 复制代码
// watch 里加判断
watch(() => props.modelValue, (val) => {
  if (val !== localVal.value) localVal.value = val
})

9️⃣ SSR/预渲染页面时,尽量避免使用异步的响应式数据源

很多人会直接在 setup() 里写异步请求,然后用于组件渲染。SSR 下可能直接输出 [object Promise] 或空数据。

建议

  • SSR 页面用 async setup() + await fetch() + return 渲染数据
  • 或者预取数据放在 <script> 里注入,全局 hydration 时解析

🔟 用好 devtools,不只是调试,还能看"慢组件"

Vue Devtools 有一个隐藏用法是:看每个组件的 render 次数和 diff 触发点

打开 Vue Devtools,切换到"性能"或"组件树",能看到组件被反复更新的次数,尤其是:

  • 有大图渲染的地方
  • 有复杂 slot 的地方
  • 大型表单组件

搭配 markRaw()shallowReactive() 甚至 defineAsyncComponent() 都能做 lazy load。


🧠性能不是优化出来的,是设计出来的

Vue3 给了我们非常灵活的响应式系统,但滥用比不用更可怕。

建议你:

  • 统一响应式策略(ref + computed + watch
  • 统一组件通信方式(props or context)
  • 加上埋点 + devtools 报表,定期做 render 追踪

📌 你可以继续看我的系列文章

相关推荐
qq_5470261792 小时前
Flowable 工作流引擎
java·服务器·前端
刘逸潇20052 小时前
CSS基础语法
前端·css
Sheldon一蓑烟雨任平生3 小时前
Vue3 插件(可选独立模块复用)
vue.js·vue3·插件·vue3 插件·可选独立模块·插件使用方式·插件中的依赖注入
吃饺子不吃馅3 小时前
[开源] 从零到一打造在线 PPT 编辑器:React + Zustand + Zundo
前端·svg·图形学
小马哥编程4 小时前
【软考架构】案例分析-Web应用设计(应用服务器概念)
前端·架构
鱼与宇4 小时前
苍穹外卖-VUE
前端·javascript·vue.js
啃火龙果的兔子4 小时前
前端直接渲染Markdown
前端
z-robot4 小时前
Nginx 配置代理
前端
用户47949283569155 小时前
Safari 中文输入法的诡异 Bug:为什么输入 @ 会变成 @@? ## 开头 做 @ 提及功能的时候,测试同学用 Safari 测出了个奇怪的问题
前端·javascript·浏览器
没有故事、有酒5 小时前
Ajax介绍
前端·ajax·okhttp