本文 AI 產出,尚未審核

Vue3 生命週期 – onBeforeMount / onBeforeUpdate / onBeforeUnmount


簡介

在 Vue 3 中,生命週期 (Lifecycle) 是每個元件在建立、更新、銷毀過程中會依序觸發的一系列鉤子函式。了解這些鉤子不僅能讓我們在正確的時機插入自訂邏輯,還能避免資源洩漏、效能瓶頸等常見問題。

本單元聚焦於 onBeforeMount、onBeforeUpdate、onBeforeUnmount 三個「即將」階段的 Hook。它們分別在「掛載前」、「更新前」與「銷毀前」被呼叫,提供我們最後一次機會去準備或清理元件。對於需要在畫面渲染前完成資料預處理、在狀態變更前做比對、或在離開頁面前釋放資源的情境,這三個 Hook 是不可或缺的工具。


核心概念

1. onBeforeMount

  • 觸發時機:在 Vue 完成 虛擬 DOM 的建立,但還未把實際 DOM 插入到頁面上時。
  • 典型用途:
    • 取得或計算 首次渲染所需的資料,但不想阻塞 setup。
    • 設定 第三方 UI 元件(如圖表、地圖)需要的容器尺寸,確保容器已存在於 DOM。
<script setup>
import { ref, onBeforeMount } from 'vue'

const chartData = ref([])

// 假設有一個取得圖表資料的 API
async function fetchChartData() {
  const res = await fetch('/api/chart')
  chartData.value = await res.json()
}

// 在掛載前先取得資料,避免畫面閃爍
onBeforeMount(() => {
  // 這裡可以使用 async/await,但 onBeforeMount 本身不支援返回 Promise
  // 因此直接呼叫非同步函式
  fetchChartData()
})
</script>

<template>
  <div id="chart-container">
    <!-- 這裡的圖表會在資料取得後自動渲染 -->
  </div>
</template>

小技巧:如果需要在 onBeforeMount 中使用 await,可將非同步函式抽離,然後在 Hook 內直接呼叫,Vue 不會等待其完成,渲染會在資料回來後自動更新。


2. onBeforeUpdate

  • 觸發時機:當 響應式狀態 變動,導致元件即將重新渲染 之前。
  • 典型用途:
    • 比對舊值與新值,決定是否真的需要執行昂貴的計算或 API 請求。
    • 暫存 UI 狀態(如滾動位置、焦點),以便在更新後恢復。
<script setup>
import { ref, onBeforeUpdate, watch } from 'vue'

const searchTerm = ref('')
const results = ref([])

// 每次搜尋前先保存當前的滾動位置
let previousScrollY = 0

onBeforeUpdate(() => {
  // 只在 searchTerm 改變時執行
  if (searchTerm.value !== previousSearch) {
    previousScrollY = window.scrollY   // 保存滾動位置
  }
})

// 監聽 searchTerm 變化,發送搜尋請求
watch(searchTerm, async (newVal, oldVal) => {
  previousSearch = oldVal
  const res = await fetch(`/api/search?q=${newVal}`)
  results.value = await res.json()
})
</script>

<template>
  <input v-model="searchTerm" placeholder="輸入關鍵字搜尋…" />
  <ul>
    <li v-for="item in results" :key="item.id">{{ item.name }}</li>
  </ul>
</template>

注意:onBeforeUpdate 只會在 實際的渲染 前被呼叫。如果在同一次更新週期內多次改變同一個響應式屬性,Hook 仍只執行一次。


3. onBeforeUnmount

  • 觸發時機:元件即將從 DOM 中移除,銷毀前。
  • 典型用途:
    • 清除事件監聽、取消訂閱、關閉 WebSocket、清除計時器。
    • 儲存使用者離開前的狀態(如表單草稿),方便下次回來時還原。
<script setup>
import { ref, onBeforeUnmount } from 'vue'

const timerId = ref(null)
const counter = ref(0)

// 每秒遞增計數器
function startTimer() {
  timerId.value = setInterval(() => {
    counter.value++
  }, 1000)
}

// 元件掛載時啟動計時器
startTimer()

// 在元件被銷毀前清除計時器,防止記憶體洩漏
onBeforeUnmount(() => {
  clearInterval(timerId.value)
  console.log('Timer cleared, component is about to be unmounted.')
})
</script>

<template>
  <p>計時器:{{ counter }} 秒</p>
</template>

最佳實踐:所有在 setup 中建立的外部資源(例如 addEventListener、setTimeout、第三方庫的實例)**必須在 onBeforeUnmount 中對應清理,否則即使元件已被移除,仍會持續佔用記憶體或觸發不必要的回呼。


常見陷阱與最佳實踐

陷阱 說明 解決方案
在 onBeforeMount 使用 await onBeforeMount 不會等待 Promise 完成,導致資料仍在渲染後才回來,出現閃爍或錯誤 UI。 把非同步邏輯抽成獨立函式,或改用 onMounted(會等 DOM 完全掛載)配合 await。
在 onBeforeUpdate 內直接改變響應式資料 會觸發無限更新迴圈,因為每次改變都會再觸發 onBeforeUpdate。 僅讀取或使用 nextTick 延遲變更,或在 watch 中處理資料變更。
忘記清理計時器或事件監聽 元件被銷毀後仍持續執行,造成記憶體洩漏或意外行為。 在 onBeforeUnmount 中統一清除所有外部資源;可將清理函式集中管理(例如返回一個 dispose 函式)。
在 onBeforeUnmount 中使用已被釋放的變數 若變數在其他地方已被設定為 null,可能拋出錯誤。 確保清理程式碼具備容錯(如 if (timerId) clearInterval(timerId)).
過度依賴 onBeforeUpdate 進行資料請求 每次 UI 更新都發起請求,會造成大量不必要的網路流量。 使用 watch 或 debounce 來限制請求頻率,僅在關鍵資料變化時才呼叫 API。

最佳實踐小結

  1. 只在必要時使用:若不需要在掛載前就取得資料,直接在 setup 或 onMounted 處理即可。
  2. 保持 Hook 純粹:Hook 應只負責「何時」執行,而非「怎麼」實作。具體邏輯建議抽成可重用的函式。
  3. 統一清理:建立一個 cleanup 陣列,將所有需要在 onBeforeUnmount 執行的函式推入,最後一次迭代執行,提升可維護性。
import { onBeforeUnmount } from 'vue'

const disposers = []

function addDisposer(fn) {
  disposers.push(fn)
}

// 範例:註冊事件
const handler = e => console.log(e)
window.addEventListener('resize', handler)
addDisposer(() => window.removeEventListener('resize', handler))

onBeforeUnmount(() => {
  disposers.forEach(fn => fn())
})

實際應用場景

  1. 圖表套件初始化

    • 在 onBeforeMount 取得容器尺寸,確保圖表在正確寬高下渲染。
    • onBeforeUnmount 釋放圖表實例,避免記憶體佔用。
  2. 表單自動保存草稿

    • 使用 onBeforeUpdate 捕捉使用者輸入變化,將內容暫存到 localStorage。
    • 在 onBeforeUnmount 把最後一次草稿寫入持久化儲存,避免遺失。
  3. 單頁應用 (SPA) 中的路由守護

    • 當離開當前頁面前,需要先向後端確認是否有未完成的操作,使用 onBeforeUnmount 發送確認請求。
  4. 即時通訊 (WebSocket) 連線

    • 在 onBeforeMount 建立連線,確保在 UI 出現前已經開始接收資料。
    • onBeforeUnmount 關閉連線,釋放資源。
  5. 動態樣式或動畫

    • onBeforeUpdate 可在狀態改變前暫停動畫,等更新完成後再恢復,避免動畫卡頓。

總結

  • onBeforeMount:元件尚未掛載到真實 DOM 前執行,適合預先取得資料、計算容器尺寸或初始化第三方套件。
  • onBeforeUpdate:在每次渲染前觸發,最適合比對舊新值、暫存 UI 狀態或限制頻繁的副作用。
  • onBeforeUnmount:元件即將被銷毀時呼叫,是清理資源、釋放記憶體的最後防線。

掌握這三個「即將」階段的 Hook,能讓我們在 Vue 3 中寫出更安全、更高效的元件。記得遵守 「只在需要的時機使用」 與 「統一清理」 的原則,讓程式碼保持乾淨、易於維護。祝你在開發 Vue 應用時,玩得開心、寫得順手!