Vue3 Composition API(核心) – 生命周期鉤子(onMounted、onUnmounted、…)
簡介
在 Vue 3 中,Composition API 讓我們可以把組件的邏輯抽離成可重用的函式(composable),而 生命周期鉤子 則是控制副作用(side‑effect)最關鍵的入口。
正確使用 onMounted、onUnmounted 等鉤子,能讓資料抓取、事件註冊、資源釋放等工作在適當的時機執行,避免記憶體泄漏或不一致的 UI。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你深入了解 Vue3 中的生命週期鉤子,並提供 實務應用 的指引,適合 初學者到中級開發者。
核心概念
1. 生命週期鉤子概覽
| 鉤子 | 觸發時機 | 常見用途 |
|---|---|---|
onBeforeMount |
組件掛載前 | 初始化變數、設定 logger |
onMounted |
組件掛載完成、DOM 已插入 | 發送 API 請求、註冊第三方插件 |
onBeforeUpdate |
資料變更前 | 捕捉即將更新的狀態 |
onUpdated |
DOM 更新完成 | 讀取或操作更新後的 DOM |
onBeforeUnmount |
組件即將卸載 | 清理計時器、取消訂閱 |
onUnmounted |
組件已卸載 | 釋放資源、移除全域事件 |
onErrorCaptured |
子組件錯誤冒泡時 | 捕獲錯誤、上報 |
onRenderTracked / onRenderTriggered |
Vue 內部渲染追蹤(較少使用) | 性能分析 |
注意:在 SSR(Server‑Side Rendering) 環境下,只有
onBeforeMount與onServerPrefetch會被呼叫,其他鉤子會被略過。
2. onMounted – 首次渲染完成
import { ref, onMounted } from 'vue'
export default {
setup() {
const users = ref([])
// 只在組件掛載後執行一次
onMounted(async () => {
const resp = await fetch('https://api.example.com/users')
users.value = await resp.json()
})
return { users }
}
}
說明:onMounted 內的程式碼會在 DOM 真正插入 後才執行,適合 發送 AJAX、初始化圖表 等需要 DOM 的操作。
3. onUnmounted – 清理工作
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const timer = ref(null)
onMounted(() => {
timer.value = setInterval(() => {
console.log('tick')
}, 1000)
})
// 組件卸載時必須清除計時器,避免 memory leak
onUnmounted(() => {
clearInterval(timer.value)
})
}
}
說明:onUnmounted 是 資源釋放 的絕佳時機,務必把所有在 onMounted 中建立的外部連結(事件、訂閱、計時器)都在此清除。
4. 其他常用鉤子
onBeforeMount
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
console.log('Component will mount soon')
})
onUpdated – DOM 更新後取得最新尺寸
import { ref, onMounted, onUpdated } from 'vue'
export default {
setup() {
const box = ref(null)
const size = ref({ width: 0, height: 0 })
const measure = () => {
if (box.value) {
size.value = {
width: box.value.offsetWidth,
height: box.value.offsetHeight
}
}
}
onMounted(measure)
onUpdated(measure) // 每次資料變動後重新測量
return { box, size }
},
template: `
<div ref="box" class="my-box">
<p>寬度:{{ size.width }}px, 高度:{{ size.height }}px</p>
</div>
`
}
onErrorCaptured – 捕捉子組件錯誤
import { onErrorCaptured } from 'vue'
onErrorCaptured((err, instance, info) => {
console.error('子組件錯誤:', err, info)
// 返回 false 可阻止錯誤繼續向上冒泡
return false
})
5. 結合 watchEffect 與生命週期
import { ref, watchEffect, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const count = ref(0)
// watchEffect 內部會自動在組件卸載時停止
const stop = watchEffect(() => {
console.log('count 改變為', count.value)
})
onMounted(() => {
const id = setInterval(() => count.value++, 1000)
// 以 onUnmounted 方式手動清除
onUnmounted(() => clearInterval(id))
})
}
}
小技巧:
watchEffect本身會在組件卸載時自動停止,但若裡面建立了外部資源(如setInterval),仍需在onUnmounted手動清除。
常見陷阱與最佳實踐
| 陷阱 | 可能的問題 | 解決方案 / 最佳實踐 |
|---|---|---|
忘記在 onUnmounted 清除副作用 |
記憶體泄漏、重複事件觸發 | 始終配對 onMounted ↔ onUnmounted,使用 try…finally 或 return 清理 |
在 onMounted 中直接使用 await,導致組件渲染阻塞 |
UI 卡頓、使用者體驗差 | 把 await 包在 非阻塞 的函式裡,或使用 Promise.then,同時顯示 loading 狀態 |
在 SSR 環境使用只能在瀏覽器執行的 API(如 window、document) |
產生 ReferenceError |
使用 onMounted 包裹,或檢查 import.meta.env.SSR |
| 多次呼叫同一鉤子,導致副作用重複 | 重複註冊事件、重複資料請求 | 集中管理:將副作用抽離成 composable,確保只在需要時呼叫一次 |
在 onErrorCaptured 中未返回 false |
錯誤會繼續向上冒泡,可能導致全局錯誤處理被觸發 | 明確回傳 false 以阻止冒泡,或自行上報錯誤 |
最佳實踐小結
- 副作用與清理必配對:
onMounted↔onUnmounted、watchEffect↔stop。 - 保持 composable 純粹:把 API 請求、WebSocket、圖表初始化等封裝成獨立函式,讓
setup只負責呼叫。 - 使用
try…catch包住 async 操作,確保錯誤不會中斷生命週期流程。 - 在 SSR 時檢查環境,避免使用只能在客戶端執行的 API。
- 適時使用
onBeforeUnmount:在某些情況下(如需要先做最後一次資料同步)可先於onUnmounted執行。
實際應用場景
A. 整合第三方圖表庫(Chart.js)
import { ref, onMounted, onUnmounted } from 'vue'
import Chart from 'chart.js/auto'
export default {
setup() {
const canvasRef = ref(null)
let chartInstance = null
onMounted(() => {
chartInstance = new Chart(canvasRef.value, {
type: 'line',
data: { /* ... */ },
options: { responsive: true }
})
})
onUnmounted(() => {
if (chartInstance) chartInstance.destroy()
})
return { canvasRef }
},
template: `<canvas ref="canvasRef"></canvas>`
}
重點:圖表必須在
onMounted取得<canvas>,而在onUnmounted釋放資源,防止記憶體泄漏。
B. WebSocket 即時通訊
import { ref, onMounted, onUnmounted } from 'vue'
export default {
setup() {
const messages = ref([])
let ws = null
onMounted(() => {
ws = new WebSocket('wss://example.com/chat')
ws.addEventListener('message', e => {
messages.value.push(e.data)
})
})
onUnmounted(() => {
ws && ws.close()
})
return { messages }
}
}
C. 表單自動保存(debounce)
import { ref, watch, onMounted, onUnmounted } from 'vue'
import { debounce } from 'lodash-es'
export default {
setup() {
const form = ref({ name: '', email: '' })
const save = debounce(() => {
console.log('自動保存資料', form.value)
// 呼叫 API...
}, 800)
const stopWatch = watch(form, save, { deep: true })
onUnmounted(() => {
stopWatch() // 停止 watch
save.cancel() // 取消 pending 的 debounce
})
return { form }
}
}
總結
Vue 3 的 Composition API 讓我們以函式的方式管理生命週期,onMounted、onUnmounted 等鉤子是控制副作用的核心。
- 正確的 配對清理 能避免記憶體泄漏與意外行為。
- 透過 抽離 composable,可讓程式碼更易維護、可重用。
- 注意 SSR 環境 以及 錯誤捕獲 的細節,能提升應用的穩定性。
掌握這些概念與實作範例,你就能在 Vue3 專案中自如地處理資料抓取、第三方套件整合、即時通訊等需求,寫出 乾淨、可維護、效能友好 的程式碼。祝開發順利!