说实话,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 追踪