Vue 性能優化總覽
「Vue 性能優化」是一個很大的題目,不能只背一串技巧。面試官真正想看的通常是:你能不能先定位瓶頸,再選對策略,最後說出取捨與驗證方式。
一句話回答:Vue 性能優化要先量測,再從首屏載入、資源體積、渲染成本、響應式成本、資料請求、快取策略、使用者體驗與渲染模式分層處理;不要一上來就套 SSR、虛擬列表或打包優化,而是根據瓶頸選方案。
一、 必考觀念
1. 性能優化不是技巧清單
很多面試回答會變成這樣:
- 路由懶載入。
- Tree Shaking。
- gzip。
- CDN。
v-if和v-show。key。- 防抖節流。
- 骨架屏。
- SSR。
這些都對,但如果只是羅列,答案會很散。
比較好的主線是:
量測問題
-> 定位瓶頸
-> 分層優化
-> 驗證結果
-> 說出副作用這個順序比「我知道很多優化手段」更像真實工程經驗。
2. 先判斷慢在哪一層
Vue 應用變慢,可能慢在不同地方:
| 層級 | 常見問題 | 代表策略 |
|---|---|---|
| 網路層 | TTFB 高、資源下載慢、快取沒命中 | CDN、HTTP cache、gzip/brotli、SSR cache |
| 資源層 | JS/CSS/圖片太大 | 分包、按需載入、圖片格式、字體優化 |
| 執行層 | JS parse/execute 太久、long task | 延後初始化、Web Worker、拆分重計算 |
| 資料層 | API 串行、重複請求、首屏阻塞 | 並行請求、快取、預取、取消請求 |
| 渲染層 | DOM 太多、Diff 成本高、Layout/Paint 重 | 虛擬列表、穩定 key、讀寫分離、降低更新範圍 |
| 響應式層 | 深層 reactive 太大、watch 過多、computed 重算 | shallowRef、markRaw、拆分狀態、避免 deep watch |
| 體驗層 | 白屏久、布局跳動、互動卡 | 骨架屏、分階段渲染、樂觀更新、避免 CLS |
| 架構層 | CSR 首屏與 SEO 不足 | SSR、SSG、預渲染、邊緣渲染 |
面試時可以先丟出這張分層圖,接著再展開你最熟的幾層。
3. 先量測,不要憑感覺優化
常用工具:
- Chrome DevTools Network:看資源大小、請求順序、快取命中。
- Chrome DevTools Performance:看 long task、JS 執行、Layout、Paint。
- Lighthouse:看 FCP、LCP、INP、CLS 等指標。
- Web Vitals / RUM:看真實使用者環境下的數據。
- Bundle Analyzer:看主包、chunk 與第三方依賴體積。
如果沒有量測,很多優化都只是「看起來很努力」。
例如:
- 主包 3MB:優先看分包、依賴與壓縮。
- API 首包 2 秒:優先看資料請求、快取與後端。
- 首屏一次渲染 5000 個節點:優先看渲染策略。
- 點擊後卡住 800ms:優先看 long task、computed/watch、同步重計算。
二、 面試回答框架
1. 用四段式回答
回答「Vue 專案如何做性能優化」時,可以這樣組織:
第一段:先量測與定位
第二段:依層級說常見優化方向
第三段:補 Vue 特有的渲染與響應式成本
第四段:說取捨、驗證與適用場景一個完整回答可以是:
我會先用 Network、Performance、Lighthouse 和 bundle analyzer 找瓶頸,而不是直接套技巧。如果是首屏慢,會看主包體積、路由懶載入、圖片字體、API 並行與快取;如果是互動卡,會看 long task、DOM 節點數、Diff 範圍、computed/watch 重算與響應式資料是否過大。Vue 層面會注意穩定 key、避免同時使用
v-if和v-for、大型列表用虛擬列表、重型組件按需載入、狀態拆分、必要時使用shallowRef或markRaw。最後會用同樣指標驗證結果,並評估拆包、快取、SSR、骨架屏等方案的副作用。
這個回答的好處是:有流程、有分類、有 Vue 特性,也有資深取捨。
2. 不同場景要有不同答案
性能優化沒有固定套餐。
| 場景 | 優先思路 |
|---|---|
| 首屏白屏久 | 分包、壓縮、圖片、API、骨架屏、SSR/SSG |
| 後台列表卡 | 虛擬列表、分頁、穩定 key、減少欄位與 watcher |
| 表單輸入卡 | 拆分組件、降低父層更新、節流驗證、避免深層 watch |
| 圖表頁慢 | 圖表庫按需載入、延後初始化、資料抽樣、Web Worker |
| Modal 首次打開慢 | 非首屏組件懶載入、預載高機率模組 |
| SEO 與公開內容頁 | SSR、SSG、預渲染、head 管理、快取 |
所以面試時最好不要只說「我會加 SSR」。SSR 是架構選型,不是通用性能開關。
三、 優化主題地圖
這個專題後面可以拆成多篇文章,每篇處理一個方向。
| 主題 | 要回答的問題 | 狀態 |
|---|---|---|
| SPA 首屏載入優化 | 白屏、LCP、bundle、API、圖片、骨架屏怎麼處理 | 已完成 |
| DOM 操作成本 | 為什麼 DOM 操作貴,Style/Layout/Paint 差在哪 | 已完成 |
| Diff 算法 | Vue 如何減少真實 DOM 操作,key 和 LIS 有什麼關係 | 已完成 |
| 渲染性能優化 | v-if/v-show、v-for、key、虛擬列表、v-memo、事件代理 | 已完成 |
| 響應式性能優化 | reactive 深度追蹤、shallowRef、markRaw、computed/watch 成本 | 已完成 |
| 打包與資源優化 | Tree Shaking、分包、第三方庫、CDN、gzip/brotli、source map | 已完成 |
| 請求、快取與體驗優化 | API 並行、快取、預取、PWA、骨架屏、樂觀更新 | 已完成 |
| SSR 實現原理 | SSR、Hydration、狀態注水、Mismatch 與架構取捨 | 已完成 |
這樣規劃的好處是:總覽文負責建立思考框架,細節文負責深入原理與實作,不會讓單篇文章變成很長的雜貨架。
四、 編碼階段優化
1. 減少不必要的響應式資料
Vue 的響應式系統會追蹤資料讀取與寫入。資料越大、層級越深、被讀取的地方越多,更新成本就可能越高。
常見做法:
- 不需要響應式的大型資料不要放進深層 reactive。
- 大型不可變資料可以考慮
shallowRef。 - 第三方實例、圖表物件、地圖物件可以考慮
markRaw。 - 避免對大型物件做 deep watch。
- 把狀態拆小,讓更新範圍更精準。
import { markRaw, shallowRef } from 'vue'
const chartInstance = shallowRef(null)
function initChart(el: HTMLElement) {
chartInstance.value = markRaw(createChart(el))
}重點不是每次都用 shallowRef,而是知道「深層響應式不是免費的」。
2. 避免 v-if 和 v-for 混用
在同一個節點上同時寫 v-if 和 v-for,可讀性差,也容易造成不必要的判斷與渲染成本。
<!-- 不建議 -->
<li
v-for="user in users"
v-if="user.active"
:key="user.id"
>
{{ user.name }}
</li>更好的做法是先把資料整理好:
const activeUsers = computed(() => {
return users.value.filter(user => user.active)
})<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>延伸讀:Vue computed 的實現原理、v-if 與 v-show 的區別。
3. 列表更新要有穩定 key
key 不是為了消除 warning,而是讓 Vue 正確識別 VNode 身份。
<UserRow
v-for="user in users"
:key="user.id"
:user="user"
/>動態列表如果用 index 當 key,插入、刪除、排序時容易造成 DOM 或元件狀態錯位,也會讓 Diff 更難做出正確判斷。
延伸讀:Vue 中 key 的作用。
4. 大型列表要改渲染策略
如果一次渲染一萬筆資料,再怎麼調 key 或 Diff 都有限。
這時要改策略:
- 分頁。
- 虛擬列表。
- 滾動到可視區域再載入。
- 降低每列組件複雜度。
- 避免每列綁定太多事件或 watcher。
這也是資深回答常見加分點:不是只在 API 層面微調,而是知道什麼時候該改渲染模型。
5. 事件與高頻操作要節流
對高頻事件要特別小心:
scrollresizemousemoveinput- 拖拉排序
- 搜尋建議
常見策略:
- 防抖:等使用者停止輸入再送請求。
- 節流:固定時間內最多執行一次。
requestAnimationFrame:把視覺更新對齊瀏覽器繪製節奏。- 事件代理:大量子元素點擊時,把事件放在共同父層處理。
五、 打包與載入優化
1. 分包與懶載入
首屏不需要的頁面與重型組件,不應該進入首屏主包。
const routes = [
{
path: '/report',
component: () => import('@/views/ReportPage.vue'),
},
]大型組件也可以按需載入:
import { defineAsyncComponent } from 'vue'
const RichEditor = defineAsyncComponent(() => {
return import('@/components/RichEditor.vue')
})適合懶載入的東西:
- 圖表。
- 地圖。
- 富文本編輯器。
- PDF 預覽。
- 低頻使用的管理頁。
- 非首屏 Modal。
2. 第三方模組按需導入
常見問題是只用到一小段功能,卻把整個庫打進主包。
// 優先按需導入
import debounce from 'lodash-es/debounce'UI library、icon、日期處理、圖表庫都要看是否支援按需導入與 Tree Shaking。
3. 壓縮、快取與 CDN
常見策略:
- JS/CSS minify。
- gzip / brotli。
- 靜態資源上 CDN。
- 檔名 hash 化。
- 靜態資源長快取。
index.html短快取或 no-cache。
典型策略:
index.html: no-cache
assets/*.js: max-age=31536000, immutable
assets/*.css: max-age=31536000, immutable4. 注意工具時代背景
如果面試資料裡提到 happypack,要知道它比較偏 webpack 時代的多執行緒打包方案。
現在如果是 Vite 專案,優化方向會更常落在:
- esbuild 預打包。
- Rollup chunk 拆分。
manualChunks。- 依賴按需導入。
- source map 策略。
- bundle analyzer。
能說出工具時代差異,會比單純背「happypack」更成熟。
六、 體驗與架構優化
1. 骨架屏與分階段渲染
骨架屏不能讓 API 真的變快,但可以改善白屏感。
好的骨架屏要注意:
- 接近真實布局。
- 避免資料回來後大幅位移。
- 不要讓骨架屏取代真正的性能修復。
分階段渲染的思路是:
先顯示核心內容
-> 再載入次要模組
-> 最後載入低優先級互動或推薦內容這通常比「所有資料都回來才顯示頁面」更好。
2. 快取要分層
快取不只一種:
- HTTP cache。
- CDN cache。
- API gateway cache。
- 前端 memory cache。
- Pinia store cache。
- IndexedDB / localStorage。
- Service Worker / PWA cache。
但快取一定要處理失效問題。否則性能變好了,資料卻可能變舊。
3. SSR、SSG、預渲染不是萬靈丹
SSR 可以改善公開內容頁的首屏與 SEO,但它也帶來成本:
- server 維運成本。
- hydration mismatch。
- server/client 環境差異。
- 跨請求狀態污染。
- 快取策略更複雜。
所以回答時可以說:
如果是公開內容頁、商品頁、SEO 頁,我會考慮 SSR 或 SSG;如果是登入後的後台系統,通常先做 CSR 的分包、快取與渲染優化,SSR 未必划算。
延伸讀:Vue SSR 的實現原理。
七、 追問題庫
Q1:性能優化第一步做什麼?
先量測。
用 Network 看資源與請求,用 Performance 看 long task、Layout、Paint,用 bundle analyzer 看依賴體積。沒有定位瓶頸前,不要直接說要 SSR 或重構。
Q2:Vue 專案卡頓一定是 Diff 慢嗎?
不一定。
更常見的是:
- 一次渲染太多 DOM。
- key 不穩定。
- computed/watch 做了重計算。
- 大物件深層響應式。
- 父組件更新牽動太多子組件。
- 第三方套件造成 layout thrashing。
Diff 只是渲染成本的一部分。
Q3:骨架屏算不算性能優化?
算感知性能優化,但它不一定降低真實載入時間。
如果主包很大、API 很慢、圖片很重,骨架屏只能減少等待焦慮,不能取代真正的資源與請求優化。
Q4:什麼時候用虛擬列表?
當列表資料量很大,且一次渲染大量 DOM 造成卡頓時。
如果只有幾十筆資料,虛擬列表可能增加複雜度,收益不一定高。它適合幾千、幾萬筆資料,尤其是高度可預測或可以估算的列表。
Q5:SSR 一定能提升性能嗎?
不一定。
SSR 通常改善首屏內容可見與 SEO,但如果 client bundle 仍然很大、hydration 很慢、API 很慢,使用者互動可用時間仍可能不好。SSR 要搭配分包、快取、資料策略與 hydration 成本控制。
八、 實作題
題目:一個 Vue 後台頁面列表切換很卡,你怎麼排查?
可以這樣回答:
- 用 Performance 錄一次切換,看卡在 JS、Layout、Paint 還是資料請求。
- 看列表一次渲染多少 DOM,是否需要分頁或虛擬列表。
- 檢查
v-for是否使用穩定 key,有沒有用 index key。 - 檢查每列是否有太多子組件、watcher、computed 或事件綁定。
- 檢查父層狀態更新是否導致整個列表重渲染。
- 檢查是否有深層 reactive 大資料或 deep watch。
- 如果有 DOM 測量或第三方表格套件,檢查是否造成強制同步佈局。
- 根據瓶頸選擇虛擬列表、拆分組件、狀態下沉、快取、節流或延後渲染。
這題的重點不是背「虛擬列表」四個字,而是能從資料、渲染、響應式和 DOM 成本一起排查。
九、 資深視角
1. 優化要能量化
好的性能優化要能回答:
- 優化前指標是多少?
- 優化後指標是多少?
- 是哪一段時間變短?
- 有沒有副作用?
- 對真實使用者是否有效?
例如「主包從 1.8MB 降到 780KB」、「LCP 從 4.2 秒降到 2.1 秒」、「列表切換 long task 從 600ms 降到 120ms」,都比「感覺快很多」更有說服力。
2. 不要用複雜度換假性能
有些優化會增加維護成本:
- 過度拆包會讓路由切換變慢。
- 過度快取會有資料一致性問題。
- 過度 memo 會讓程式更難理解。
- 過早上 SSR 會提高部署與除錯成本。
- 虛擬列表會增加高度計算、滾動同步與可及性問題。
資深回答要能說出:我知道這個方案有什麼副作用,所以我會在什麼條件下才用。
3. 性能優化要和產品路徑對齊
不是每個頁面都值得花同樣成本優化。
優先級通常是:
- 首屏與高流量頁。
- 付費或轉換關鍵頁。
- 使用者每天高頻操作的頁面。
- 影響互動體驗的卡頓點。
- SEO 重要的公開內容頁。
工程時間有限,性能優化要服務產品目標。
總結
| 面向 | 回答重點 |
|---|---|
| 方法論 | 先量測、再定位、分層優化、最後驗證 |
| 首屏 | 分包、壓縮、圖片、API、快取、骨架屏、SSR/SSG |
| 渲染 | 減少 DOM、穩定 key、虛擬列表、降低更新範圍 |
| 響應式 | 避免大型深層 reactive、避免 deep watch、拆分狀態 |
| 打包 | Tree Shaking、按需導入、manual chunks、source map 策略 |
| 請求 | 並行、取消、快取、預取、避免首屏阻塞 |
| 體驗 | 骨架屏、分階段渲染、樂觀更新、避免 CLS |
| 架構 | CSR、SSR、SSG、預渲染依場景取捨 |
一句完整的面試回答可以是:
Vue 性能優化我會先量測瓶頸,再分層處理。如果是首屏慢,會看 bundle、路由懶載入、圖片字體、API 並行、快取與 SSR/SSG;如果是互動卡,會看 long task、DOM 節點數、Diff 範圍、computed/watch 重算、響應式資料是否過大。Vue 特有的部分會注意穩定 key、避免不必要的深層 reactive、合理使用
v-if/v-show、大型列表用虛擬列表、重型組件按需載入。最後會用同樣的性能指標驗證效果,並評估拆包、快取、SSR 等方案帶來的複雜度。