🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
今天來分享 10 個 Vue3 的性能優化技巧。
核心原則:
減少不必要的響應式追蹤
避免無謂的 DOM 操作
按需加載資源
咱也不要為了優化而優化!小項目用默認寫法完全沒問題,優化應在性能瓶頸出現後進行。
這些技巧不難,但都非常關鍵。 看完你會發現:原來 Vue3 還能這麼寫。
1. 使用 shallowReactive 替代 reactive
問題:
reactive 會讓對象裏每一層都變得“敏感”——哪怕你只改了最裏面的某個小字段,Vue 也會花力氣去追蹤它。數據一大,性能就變慢。
解決方案:
對不需要深層響應的數據,使用 shallowReactive,只讓最外層變成響應式的。
示例:
import { shallowReactive } from 'vue';
const data = shallowReactive({
list: [],
meta: { total: 0 }
});
適用場景:
當你從後端拿到一大坨只讀數據(比如表格列表、API 響應),且不會修改嵌套屬性時。
2. 用 toRefs 解構響應式對象
問題:
如果你直接從 reactive 對象裏解構變量(如 const { name } = state),這個 name 就變成普通變量了,修改它不會觸發頁面更新。
解決方案:
使用 toRefs 解構,保持每個屬性的響應性。
示例:
const state = reactive({ name: 'Vue', age: 3 });
const { name, age } = toRefs(state); // name 和 age 依然是響應式的!
好處:
在模板中可以直接寫 {{ name }},不用寫 {{ state.name }},代碼更清爽。
3. 優先使用 watchEffect 而非 watch
區別:
watch:你要手動指定監聽誰(比如watch(count, ...))。watchEffect:你只寫邏輯,Vue 自動分析裏面用了哪些響應式變量,並監聽它們。
示例:
watchEffect(() => {
// Vue 自動發現 count.value 被用了 → 只要 count 變,這段就執行
localStorage.setItem('count', count.value);
});
適合場景:
保存用户輸入到本地緩存、根據篩選條件自動請求數據、同步狀態到 URL 等。
4. 利用 <Suspense> 優雅處理異步組件
問題:
動態加載組件(如通過 import())時,頁面可能白屏幾秒,用户體驗差。
解決方案:
用 <Suspense> 包裹異步組件,顯示 loading 提示。
示例:
<Suspense>
<template #default>
<UserProfile /> <!-- 必須是異步組件 -->
</template>
<template #fallback>
<div>加載中,請稍候…</div>
</template>
</Suspense>
注意:
僅適用於異步組件(即用 defineAsyncComponent 或 () => import(...) 定義的組件)。
5. 使用 <Teleport> 解決模態框層級問題
問題:
彈窗寫在組件內部,可能被父級的 overflow: hidden 或 z-index 限制,導致顯示不全或蓋不住其他內容。
解決方案:
用 <Teleport> 把組件“傳送”到 <body> 底部,脱離當前 DOM 樹。
示例:
<Teleport to="body"> <Modal v-if="show" /> </Teleport>
類比:
就像你在客廳寫了個氣球,但它實際飄到了天空——不受房間天花板限制。
常用目標:to="body" 是最常見用法。
6. 自定義指令封裝高頻操作(如複製)
問題:
複製文本、防抖點擊、自動聚焦……這些功能到處都要用,每次都寫一堆代碼很麻煩。
解決方案:
寫一個自定義指令,一次定義,處處使用。
示例:
app.directive('copy', {
mounted(el, binding) {
el.addEventListener('click', () => {
navigator.clipboard.writeText(binding.value);
});
}
});
使用:
<button v-copy="'要複製的內容'">點我複製</button>
好處:邏輯集中、複用性強、模板乾淨。
7. 用 Pinia 插件擴展 store 能力
問題:
每個 store 都想加個“重置”功能?手動一個個寫太重複。
解決方案:
通過 Pinia 插件,一次性給所有 store 添加 $reset() 方法。
正確實現:
pinia.use(({ store }) => {
// 保存初始狀態快照(深拷貝)
const initialState = JSON.parse(JSON.stringify(store.$state));
store.$reset = () => {
store.$state = initialState;
};
});
使用:
const userStore = useUserStore(); userStore.$reset(); // 恢復初始狀態
適用場景:表單重置、清除緩存、統一日誌等。
注意:不能直接用
store.$patch(store.$state),因為$state是當前狀態,不是初始狀態!
8. v-memo 優化大型列表渲染
問題:
列表有上千項,哪怕只改了一行的狀態,Vue 默認會重新比對整張表,浪費性能。
解決方案:
用 v-memo 告訴 Vue:“只有這些值變了,才需要重新渲染這一行”。
示例:
<li v-for="item in list" :key="item.id" v-memo="[item.id, item.status]">
{{ item.name }} —— 狀態:{{ item.status }}
</li>
注意事項:
- 適合內容穩定、更新頻率低的大列表。
- 不要和
<transition-group>一起用(會失效)。 - 高頻變動的列表慎用,可能適得其反。
v-memo是 Vue 3.2+ 的功能。
9. 虛擬滾動(Virtual Scrolling)
問題:
渲染 10,000 條消息?瀏覽器直接卡死!
解決方案:
只渲染“當前可見區域”的內容,滑動時動態替換,內存和性能都省下來。
推薦庫(Vue 3 兼容):
vueuc(輕量、活躍維護)vue-virtual-scroll-grid
安裝 & 示例(以 vueuc 為例):
npm install vueuc
<script setup>
import { VirtualList } from 'vueuc';
</script>
<template>
<VirtualList :items="messages" :item-height="60" :bench="10">
<template #default="{ item }">
<MessageItem :msg="item" />
</template>
</VirtualList>
</template>
類比:
就像微信聊天記錄——你往上滑,舊消息才加載;不滑的時候,幾千條其實沒真畫出來。
10. 路由與組件懶加載 + 圖片優化
組件懶加載
原理:不是一打開網頁就加載所有頁面,而是“用到哪個才加載哪個”。
寫法:
{ path: '/about', component: () => import('./views/About.vue') }
好處:首屏加載更快,節省流量和內存。
圖片優化
- 用 WebP 格式:比 JPG/PNG 小 30%~50%,清晰度不變(現代瀏覽器都支持)。
- 圖片懶加載:屏幕外的圖先不加載,滑到附近再加載。
- 關鍵圖預加載:首頁 Banner 圖提前加載,避免白塊。
簡單懶加載(原生支持):
<img src="image.jpg" loading="lazy" alt="示例圖" />
兼容性提示:
loading="lazy"在 Chrome/Firefox/Edge 支持良好,但 Safari 15.4 以下和 IE 不支持。若需兼容舊環境,建議搭配IntersectionObserver或第三方庫(如lazysizes)。