本文 AI 產出,尚未審核

Vue3 生命週期 – onActivated / onDeactivated(配合 <keep-alive>


簡介

在單頁應用(SPA)中,切換路由或在同一視圖內切換子組件是常見的行為。Vue 3 提供的 <keep-alive> 能夠「快取」組件的實例,避免每次切換時重新建立與銷毀,提升切換效能與使用者體驗。

然而,快取的同時也帶來了新需求:當組件被重新激活或停用時,需要執行特定的程式邏輯(例如重新取得資料、清除計時器、恢復 UI 狀態)。這正是 onActivatedonDeactivated 兩個生命週期鉤子的用武之地。

本文將深入說明這兩個鉤子在 Vue 3 中的運作原理、使用方式、常見陷阱,並提供實務範例,幫助你在實際專案中正確、有效地運用它們。


核心概念

1. 為什麼需要 keep-alive

<keep-alive> 是一個抽象的內建組件,會將它包住的動態組件(<component :is="..."> 或路由視圖)緩存起來。

  • 快取實例:組件的 setupdatacomputedwatch 等只會在第一次掛載時執行一次。
  • 保留 DOM:組件的虛擬 DOM 會被保留在內存中,切換回來時直接復原,避免重新渲染。
<keep-alive>
  <router-view v-slot="{ Component }">
    <component :is="Component" />
  </router-view>
</keep-alive>

2. onActivatedonDeactivated 的定位

鉤子 何時觸發 常見用途
onActivated 組件 從快取狀態 被重新激活(掛載到 DOM)時 重新發送 API 請求、恢復計時器、聚焦輸入框
onDeactivated 組件 被快取(從 DOM 中移除,但實例仍在)時 暫停輪詢、清除定時器、保存表單暫存資料

注意:這兩個鉤子只會在組件被 <keep-alive> 包住時生效,普通的組件掛載/卸載仍使用 onMounted / onUnmounted

3. 基本語法

import { onActivated, onDeactivated } from 'vue'

export default {
  setup() {
    onActivated(() => {
      console.log('Component activated')
    })

    onDeactivated(() => {
      console.log('Component deactivated')
    })
  }
}

onActivatedonDeactivated 只能在 setup 函式或 Composition API<script setup> 中使用。

4. 與路由結合的實作

在使用 Vue Router 時,只要把 <router-view> 包在 <keep-alive>,每個路由組件都會自動獲得激活/停用的機會。

<!-- App.vue -->
<template>
  <keep-alive include="Home,About">
    <router-view />
  </keep-alive>
</template>

includeexcludemax 等屬性可以控制哪些組件需要被快取。


程式碼範例

以下提供 五個 常見情境的完整範例,說明如何在實務開發中運用 onActivated / onDeactivated

範例 1️⃣:切換頁籤時保留表單內容

<!-- TabA.vue -->
<script setup>
import { ref, onActivated, onDeactivated } from 'vue'

const name = ref('')

// 當組件被激活時,自動把暫存的值寫回表單
onActivated(() => {
  const saved = sessionStorage.getItem('tabA-name')
  if (saved) name.value = saved
})

// 當組件被停用時,將表單暫存起來
onDeactivated(() => {
  sessionStorage.setItem('tabA-name', name.value)
})
</script>

<template>
  <input v-model="name" placeholder="輸入姓名" />
</template>

說明:使用 sessionStorage 暫存使用者在切換頁籤時的輸入,返回時自動恢復,提升使用者體驗。


範例 2️⃣:輪詢資料的暫停與恢復

// Dashboard.vue
<script setup>
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue'
import axios from 'axios'

const stats = ref(null)
let timerId = null

const fetchStats = async () => {
  const { data } = await axios.get('/api/stats')
  stats.value = data
}

// 初次掛載時立刻抓一次
onMounted(fetchStats)

// 進入激活狀態時開始 5 秒輪詢
onActivated(() => {
  timerId = setInterval(fetchStats, 5000)
})

// 離開(被快取)時停止輪詢
onDeactivated(() => {
  clearInterval(timerId)
  timerId = null
})

// 為保險起見,組件真的被銷毀時也清除定時器
onUnmounted(() => clearInterval(timerId))
</script>

<template>
  <div v-if="stats">
    <p>使用者數:{{ stats.users }}</p>
    <p>交易量:{{ stats.volume }}</p>
  </div>
</template>

說明:透過 onActivated 開始輪詢,onDeactivated 停止,避免頁面隱藏時仍持續發送請求浪費資源。


範例 3️⃣:自動聚焦輸入框

<!-- SearchBox.vue -->
<script setup>
import { ref, onActivated } from 'vue'

const query = ref('')
const inputRef = ref(null)

onActivated(() => {
  // 組件被激活後立即聚焦
  inputRef.value?.focus()
})
</script>

<template>
  <input
    ref="inputRef"
    v-model="query"
    placeholder="搜尋關鍵字"
    @keyup.enter="$emit('search', query)"
  />
</template>

說明:在切換頁籤或返回搜尋頁時,自動把焦點放回搜尋框,提升使用者操作流暢度。


範例 4️⃣:保留滾動位置

// LongList.vue
<script setup>
import { ref, onActivated, onDeactivated } from 'vue'

const scrollPos = ref(0)
const listRef = ref(null)

onActivated(() => {
  // 恢復上一次離開時的滾動位置
  listRef.value?.scrollTo(0, scrollPos.value)
})

onDeactivated(() => {
  // 記錄離開時的滾動位置
  scrollPos.value = listRef.value?.scrollTop || 0
})
</script>

<template>
  <div ref="listRef" style="height: 400px; overflow-y: auto;">
    <!-- 假設有很多項目 -->
    <div v-for="i in 200" :key="i">項目 {{ i }}</div>
  </div>
</template>

說明:使用 onDeactivated 記錄捲軸位置,onActivated 再度回到該位置,避免使用者在返回列表時必須重新捲動。


範例 5️⃣:條件快取(include / exclude)結合鉤子

<!-- App.vue -->
<template>
  <keep-alive :include="cachedNames">
    <router-view />
  </keep-alive>
</template>

<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'

const cachedNames = ref(['Home', 'Profile']) // 只快取這兩個頁面

// 透過路由 meta 控制是否需要在激活時重新載入資料
</script>
// Home.vue
<script setup>
import { onActivated } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

onActivated(() => {
  // 只在 meta.reload 為 true 時重新抓資料
  if (route.meta.reload) {
    // fetchHomeData()
  }
})
</script>

說明:結合 <keep-alive>include 屬性與 onActivated,讓開發者可以在特定條件下才執行重新載入,避免不必要的請求。


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記在 onDeactivated 清除資源 定時器、WebSocket、訂閱等會持續佔用記憶體或網路,導致記憶體洩漏或重複請求。 必須onDeactivatedclearIntervalunsubscribeclose 等,並在 onUnmounted 再次保險一次。
onActivated 直接使用非同步資料 若在激活時直接 await,可能會阻塞 UI,使用者感覺卡頓。 使用 非阻塞 的方式(如先顯示舊資料,然後在背景更新),或在 onActivated 中啟動輪詢而非一次性 await
快取過多組件 keep-alive 會把組件實例保留在記憶體,過多快取會導致記憶體使用量激增。 透過 maxincludeexclude 屬性限制快取數量,或根據路由 meta 動態決定是否快取。
onActivated 中使用 this(Options API) onActivated 只在 Composition API 中提供;若使用 Options API,必須在 activated 鉤子裡寫。 若仍使用 Options API,改用 activated() / deactivated() 生命週期選項。
忘記 keep-alive 包裹組件 沒有 <keep-alive> 包住,onActivated / onDeactivated 永遠不會被呼叫。 確認外層已正確使用 <keep-alive>,且 include/exclude 不把目標組件排除。

最佳實踐

  1. 最小化快取:只對「切換頻繁且需要保留狀態」的組件使用 keep-alive
  2. onActivated 中只做「必要」的事:如重新請求資料、恢復 UI 狀態,避免在此執行大量計算。
  3. 保持對資源的明確管理:每個在 onActivated 建立的資源(計時器、訂閱)都應在 onDeactivated 釋放。
  4. 使用 include/exclude/max 控制快取,配合路由的 meta 旗標,讓快取策略更具彈性。
  5. 測試記憶體佔用:在開發階段使用 Chrome DevTools 的 Memory 面板,觀察快取組件是否被正確釋放。

實際應用場景

場景 為什麼需要 keep-alive + onActivated/Deactivated
商品列表 → 商品詳情 → 回到列表 列表頁面的滾動位置、篩選條件、已載入的分頁資料都需要保留,避免再次向 API 請求。
聊天視窗 當使用者切換至其他頁面時,聊天視窗仍保持連線(WebSocket),但 UI 暫時隱藏;返回時立即顯示最新訊息。
儀表板的即時圖表 圖表在切換到其他頁籤時暫停輪詢,返回時重新啟動,降低不必要的網路流量。
表單編輯 表單在切換不同編輯頁面時,保留使用者已輸入的資料,避免因重新掛載而遺失。
搜尋結果頁 使用者在搜尋結果頁點擊項目查看細節,再返回時希望搜尋關鍵字與結果仍在,且輸入框自動聚焦。

總結

onActivatedonDeactivated 是 Vue 3 為 <keep-alive> 所提供的 生命週期鉤子,讓開發者在組件被快取與重新激活時,能夠精準地控制資源、狀態與 UI 行為。正確使用這兩個鉤子可以:

  • 提升效能:避免不必要的重新渲染與資料請求。
  • 改善使用者體驗:保留滾動、表單、焦點等狀態,讓切換感覺更自然。
  • 降低資源浪費:在停用時主動釋放計時器、訂閱等,避免記憶體泄漏。

在實務開發中,建議 只對需要保留狀態的組件 使用 <keep-alive>,並配合 includeexcludemax 等屬性做細粒度控制。搭配 onActivated / onDeactivated 進行 資源的初始化與清理,即可在保持效能的同時,提供流暢且一致的使用者體驗。

快去把這些技巧應用到你的 Vue3 專案中,讓你的單頁應用更快、更穩、更好用吧! 🚀