Skip to content

Vue 3 生命週期實戰:核心場景與程式碼寫法

Vue 3 生命週期實戰:onMounted 與 onUpdated

熟悉了 生命週期各階段的觸發順序 之後,接下來的問題就是實務上該把程式碼寫在哪一個鉤子裡。以下是開發中最常使用的三個核心階段,我們來看看對應的程式碼該如何撰寫。

初始化與 API 請求:setup 與 onMounted

  • setup:適合用來初始化響應式變數,例如:ref()reactive() 或呼叫不依賴 DOM 的初始邏輯。
  • onMounted:此時虛擬 DOM 已轉換為實體 DOM,適合用來操作 DOM,例如:初始化網頁圖表 Chart.js 或發送非同步 API 請求。
js
const isLoading = ref(true);
const userData = ref(null);

// 1. 同步執行於 setup 階段
console.log('元件初始化開始...');

// 2. 組件掛載完成後觸發
onMounted(async () => {
  console.log('DOM 已就緒,準備撈取資料');
  try {
    const response = await fetch('/api/user/profile');
    userData.value = await response.json();
  } finally {
    isLoading.value = false;
  }
});

這裡有三個容易忽略的細節:

  • onMounted 的回呼 可以是 async 函式,Vue 不會等待它完成,只負責在掛載時呼叫。因此在 await 之後才更新 userData,畫面依然會自動重繪。
  • 關閉載入狀態要寫在 finally,這樣 請求失敗時也會結束載入,畫面不會永遠卡在轉圈圈。
  • 如果請求結果只是拿來顯示、不需要碰 DOM,其實 寫在 setup 就能更早發出請求,讓使用者更快看到資料。

響應式狀態的變更觀測:onBeforeUpdate 與 onUpdated

當元件內部的響應式資料被修改時,Vue 會準備重新繪製畫面。這兩個鉤子可以讓您精準捕捉畫面重繪前與重繪後的瞬間。

js
const counter = ref(0);

onBeforeUpdate(() => {
  console.log('資料已變更,但畫面(DOM)尚未重新渲染');
});

onUpdated(() => {
  console.log('畫面已完成重繪!目前的實體 DOM 已反映最新數據');
});

實務上最典型的用途是 跨越重繪保存 DOM 狀態,例如:在 onBeforeUpdate 記下聊天室的捲軸位置,等 onUpdated 後再依需要捲到底部。

這兩個鉤子不是用來追蹤特定資料的

它們只告訴您 畫面重繪了,無法分辨是哪一筆資料造成的,而且沒有造成重繪的修改根本不會觸發。要追蹤特定資料的變化,請改用 watch;只是要算出一個新值拿來顯示,則用 computed。另外 絕對不要在 onUpdated 裡修改響應式狀態,那會形成無窮更新迴圈。

防止記憶體外洩:onBeforeUnmount 與 onUnmounted

當網頁切換路由或關閉特定元件時,會觸發銷毀階段。如果我們在元件內建立了全域事件監聽(如 window.addEventListener)或定時器(setInterval),必須在此階段手動清除,否則會引發記憶體外洩(Memory Leak)。

js
let timer = null;

const handleResize = () => {
  console.log('視窗大小改變中:', window.innerWidth);
};

onMounted(() => {
  // 建立全域監聽與計時器
  window.addEventListener('resize', handleResize);
  timer = setInterval(() => {
    console.log('每秒執行一次的任務...');
  }, 1000);
});

// 元件即將銷毀前,確實做好打掃工作
onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize);
  clearInterval(timer);
  console.log('全域資源已釋放');
});

handleResize 之所以要 抽成具名函式,是因為 removeEventListener 必須傳入 與註冊時相同的函式參考 才解除得掉;若當初寫成匿名箭頭函式,就再也沒有機會移除它。

判斷什麼該清、什麼不用清,只要問一句:這個資源是 Vue 建立的嗎?

需要手動清除會自動清除
setIntervalsetTimeoutsetup 中同步建立的 watchwatchEffect
windowdocument 的事件監聽computed 的依賴追蹤
WebSocket、EventSource 連線模板上用 v-on 綁定的事件
第三方套件建立的實體,例如:圖表、地圖子元件與其專屬的監聽器

把清理邏輯搬進 Composable

同一組 掛載時建立、卸載時清除 的程式碼若在多個元件重複出現,很適合抽成 Composable。鉤子可以註冊多次,抽出去之後元件端只要呼叫一行,也不會再忘記清理。

常見問題

為什麼 onMounted 的回呼可以加上 async?

可以,Vue 不會等待這個 Promise,它只負責在掛載完成時呼叫函式。因此在 await 之後更新響應式狀態是安全的,畫面會在資料回來後自動重繪。

載入中狀態為什麼要用 finally 收尾?

因為請求失敗時也必須把載入狀態關掉,否則畫面會永遠停在載入中。finally 不論成功或拋出錯誤都會執行,是關閉 isLoading 最安全的位置。

onUpdated 每次資料變動都會觸發嗎?

只有實際造成畫面重繪時才會觸發。若修改的資料沒有被模板使用,或新值與舊值相同,Vue 不會重新渲染,這兩個鉤子也就不會執行。

想知道某個資料變了,該用 onUpdated 還是 watch?

watchonUpdated 只告訴您畫面重繪了,無法分辨是哪個資料造成的,它適合用來讀取更新後的 DOM。要追蹤特定資料的變化就用 watch 指定來源。

元件裡的 watch 與 computed 也需要手動清除嗎?

setup 中同步建立的不需要,Vue 會在元件卸載時自動停止。需要手動處理的是 Vue 管不到的資源,例如:setIntervalwindow 事件監聽、WebSocket 連線與第三方套件的實體。

沒有清除計時器會發生什麼事?

元件雖然從畫面上消失,setInterval 仍會持續執行並抓著元件的變數不放,導致記憶體無法回收。反覆進出該頁面會累積多個計時器,造成效能持續下滑。

延伸閱讀