跳至主要內容
Skip to content

深入 Vue KeepAlive:實現高效能組件緩存與狀態保留

在開發單頁應用 (SPA) 時,我們經常會遇到「切換視圖後狀態消失」的問題。例如:使用者在列表頁捲動到一半,點進詳情頁後再返回,列表頁卻回到了頂部;或是複雜的表單填寫到一半,切換分頁後內容全數遺失。

Vue 3 提供的 KeepAlive 內建組件正是為了解決這些痛點而生。它能將組件實例「保存」在記憶體中,避免重複的卸載與重新掛載,從而保留狀態並大幅提升切換效能。


一、 KeepAlive 的核心概念

KeepAlive 是一個抽象組件,它本身不會被渲染為 DOM 元素,也不會出現在組件父子鏈中。它的作用是在動態組件切換時,對其內部的組件進行「緩存」。

運作原理

當一個組件在 KeepAlive 中被切換掉時,它不會進入銷毀 (Unmount) 流程,而是進入一個稱為「失活 (Deactivated)」的狀態,並被存儲在快取池中。當再次切換回該組件時,它會從快取中被取出並重新「激活 (Activated)」,而不是重新創建。


二、 基礎用法與配置

我們可以使用 includeexcludemax 屬性來精確控制緩存行為。

1. 條件緩存:include 與 exclude

這兩個屬性允許我們指定哪些組件需要被緩存。它們接收以逗號分隔的字串、正規表達式或陣列。

typescript
// 僅緩存 A 與 B 組件
<KeepAlive :include="['ComponentA', 'ComponentB']">
  <component :is="view" />
</KeepAlive>

// 排除 C 組件
<KeepAlive exclude="ComponentC">
  <component :is="view" />
</KeepAlive>

IMPORTANT

KeepAlive 判斷組件名稱時,會優先匹配組件自身的 name 選項。如果使用 <script setup>,請確保組件有名稱定義或檔案名稱正確。

2. 緩存數量限制:max

當緩存的組件過多時,會佔用大量記憶體。透過 max 屬性,我們可以限制快取池的最大容量。當容量滿了時,Vue 會使用 LRU (Least Recently Used) 演算法,自動銷毀最久未使用的組件。

typescript
<KeepAlive :max="10">
  <component :is="view" />
</KeepAlive>

三、 專屬生命週期:Activated 與 Deactivated

由於被緩存的組件不會觸發 onMountedonUnmounted,Vue 為其提供了專屬的生命週期鉤子。

1. 生命週期鉤子說明

鉤子名稱觸發時機常見用途
onActivated組件被掛載或從緩存取出時刷新數據、重置捲軸位置、啟動計時器
onDeactivated組件被移除但進入緩存時暫停動畫、停止計時器、取消 WebSocket 訂閱

2. 實作範例

typescript
<script setup lang="ts">
import { onActivated, onDeactivated, ref } from 'vue'

const scrollY = ref(0)
const containerRef = ref<HTMLElement | null>(null)

onActivated(() => {
  // 返回組件時恢復捲軸位置
  if (containerRef.value) {
    containerRef.value.scrollTop = scrollY.value
  }
  console.log('組件已激活')
})

onDeactivated(() => {
  // 離開組件時記錄捲軸位置
  if (containerRef.value) {
    scrollY.value = containerRef.value.scrollTop
  }
  console.log('組件已進入緩存')
})
</script>

四、 進階實戰:效能優化與常見陷阱

在實務中,我們必須考慮組件的記憶體佔用與數據更新時機。

1. 記憶體管理 (Memory Management)

雖然 KeepAlive 很方便,但濫用會導致瀏覽器記憶體持續攀升。

  • 策略:僅緩存那些「重建代價高」或「狀態需要保留」的組件,如深層嵌套的樹狀結構或多步表單。
  • 適配:在行動裝置上,建議將 max 設得更小(如 3-5),以避免低階手機系統因記憶體不足而強制分頁更新。

2. 數據新鮮度檢查

如果你在 onActivated 中請求數據,雖然用戶感覺載入變快了,但如果數據變動頻繁,這反而是正確的時機。

typescript
onActivated(async () => {
  // 判斷是否需要重新抓取數據
  if (shouldUpdate.value) {
    await fetchData()
  }
})

3. 與 Transition 整合

實現視圖切換時的流暢轉場效果:

typescript
<router-view v-slot="{ Component }">
  <transition name="fade" mode="out-in">
    <KeepAlive>
      <component :is="Component" />
    </KeepAlive>
  </transition>
</router-view>

五、 技術實務:常見錯誤處理

1. 緩存失效問題

最常見的問題是組件沒有被緩存。這通常是因為 include 匹配失敗。

typescript
// 錯誤診斷檢查
function checkComponentName(vm) {
  if (!vm.type.name && !vm.type.__name) {
    console.warn('警告:KeepAlive 內的組件缺少名稱定義,可能會導致緩存失效')
  }
}

2. 生命週期順序陷阱

請注意,onActivated 在首次掛載時也會緊跟在 onMounted 之後觸發。我們需要確保初始化邏輯不會執行兩遍。


總結

KeepAlive 是 Vue 提供給開發者的效能利器,下表整理了它的使用核心:

核心點說明
適用範圍需要保留狀態、頻繁切換的動態視圖。
屬性配置使用 include/exclude 過濾,max 限制內存佔用。
生命週期務必在 onActivated 處理與「重新進入」相關的邏輯。
性能考量避免過度緩存,建議配合 LRU 策略 (max)。

進階挑戰

  1. 捲軸記憶:試著實作一個列表頁面,使用 KeepAlive 並配合 onActivated 完美還原用戶離開時的捲軸位置。
  2. 多級緩存:在嵌套路由 (children) 中,嘗試實現僅緩存特定子路由的邏輯。
  3. 動態 include:根據用戶的操作權限,動態地修改 KeepAliveinclude 陣列。

延伸閱讀與資源