Vue3 生命週期 – onActivated / onDeactivated(配合 <keep-alive>)
簡介
在單頁應用(SPA)中,切換路由或在同一視圖內切換子組件是常見的行為。Vue 3 提供的 <keep-alive> 能夠「快取」組件的實例,避免每次切換時重新建立與銷毀,提升切換效能與使用者體驗。
然而,快取的同時也帶來了新需求:當組件被重新激活或停用時,需要執行特定的程式邏輯(例如重新取得資料、清除計時器、恢復 UI 狀態)。這正是 onActivated 與 onDeactivated 兩個生命週期鉤子的用武之地。
本文將深入說明這兩個鉤子在 Vue 3 中的運作原理、使用方式、常見陷阱,並提供實務範例,幫助你在實際專案中正確、有效地運用它們。
核心概念
1. 為什麼需要 keep-alive
<keep-alive> 是一個抽象的內建組件,會將它包住的動態組件(<component :is="..."> 或路由視圖)緩存起來。
- 快取實例:組件的
setup、data、computed、watch等只會在第一次掛載時執行一次。 - 保留 DOM:組件的虛擬 DOM 會被保留在內存中,切換回來時直接復原,避免重新渲染。
<keep-alive>
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</keep-alive>
2. onActivated 與 onDeactivated 的定位
| 鉤子 | 何時觸發 | 常見用途 |
|---|---|---|
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')
})
}
}
onActivated與onDeactivated只能在 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>
include、exclude、max 等屬性可以控制哪些組件需要被快取。
程式碼範例
以下提供 五個 常見情境的完整範例,說明如何在實務開發中運用 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、訂閱等會持續佔用記憶體或網路,導致記憶體洩漏或重複請求。 | 必須 在 onDeactivated 中 clearInterval、unsubscribe、close 等,並在 onUnmounted 再次保險一次。 |
在 onActivated 直接使用非同步資料 |
若在激活時直接 await,可能會阻塞 UI,使用者感覺卡頓。 |
使用 非阻塞 的方式(如先顯示舊資料,然後在背景更新),或在 onActivated 中啟動輪詢而非一次性 await。 |
| 快取過多組件 | keep-alive 會把組件實例保留在記憶體,過多快取會導致記憶體使用量激增。 |
透過 max、include、exclude 屬性限制快取數量,或根據路由 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 不把目標組件排除。 |
最佳實踐
- 最小化快取:只對「切換頻繁且需要保留狀態」的組件使用
keep-alive。 - 在
onActivated中只做「必要」的事:如重新請求資料、恢復 UI 狀態,避免在此執行大量計算。 - 保持對資源的明確管理:每個在
onActivated建立的資源(計時器、訂閱)都應在onDeactivated釋放。 - 使用
include/exclude/max控制快取,配合路由的meta旗標,讓快取策略更具彈性。 - 測試記憶體佔用:在開發階段使用 Chrome DevTools 的 Memory 面板,觀察快取組件是否被正確釋放。
實際應用場景
| 場景 | 為什麼需要 keep-alive + onActivated/Deactivated |
|---|---|
| 商品列表 → 商品詳情 → 回到列表 | 列表頁面的滾動位置、篩選條件、已載入的分頁資料都需要保留,避免再次向 API 請求。 |
| 聊天視窗 | 當使用者切換至其他頁面時,聊天視窗仍保持連線(WebSocket),但 UI 暫時隱藏;返回時立即顯示最新訊息。 |
| 儀表板的即時圖表 | 圖表在切換到其他頁籤時暫停輪詢,返回時重新啟動,降低不必要的網路流量。 |
| 表單編輯 | 表單在切換不同編輯頁面時,保留使用者已輸入的資料,避免因重新掛載而遺失。 |
| 搜尋結果頁 | 使用者在搜尋結果頁點擊項目查看細節,再返回時希望搜尋關鍵字與結果仍在,且輸入框自動聚焦。 |
總結
onActivated 與 onDeactivated 是 Vue 3 為 <keep-alive> 所提供的 生命週期鉤子,讓開發者在組件被快取與重新激活時,能夠精準地控制資源、狀態與 UI 行為。正確使用這兩個鉤子可以:
- 提升效能:避免不必要的重新渲染與資料請求。
- 改善使用者體驗:保留滾動、表單、焦點等狀態,讓切換感覺更自然。
- 降低資源浪費:在停用時主動釋放計時器、訂閱等,避免記憶體泄漏。
在實務開發中,建議 只對需要保留狀態的組件 使用 <keep-alive>,並配合 include、exclude、max 等屬性做細粒度控制。搭配 onActivated / onDeactivated 進行 資源的初始化與清理,即可在保持效能的同時,提供流暢且一致的使用者體驗。
快去把這些技巧應用到你的 Vue3 專案中,讓你的單頁應用更快、更穩、更好用吧! 🚀