本文 AI 產出,尚未審核

Vue3 – Lifecycle Hooks(生命週期)

主題:setup() 之前與之後


簡介

在 Vue 3 中,Composition APIsetup() 為核心,取代了 Options API 中的多數生命週期寫法。了解 setup() 前後會觸發哪些 hook,才能在正確的時機取得資料、掛載事件或釋放資源。
如果把 setup() 想成「元件的建構子」,那麼它前面的 hook(beforeCreatecreated)是元件「還未完成初始化」的階段;setup() 後的 hook(onBeforeMountonMountedonBeforeUnmount…)則是「DOM 已經可操作」或「即將被銷毀」的階段。掌握這些時序,有助於:

  • 避免在錯誤的時機存取 DOM(會得到 null)。
  • 正確地初始化或清理副作用(如訂閱、計時器)。
  • 提升元件的可測試性與可維護性,因為副作用集中在明確的 hook 中。

以下內容會說明 setup() 前後的生命週期順序、實作方式以及常見陷阱,並提供多個實用範例,讓你在開發 Vue 3 應用時,能夠得心應手。


核心概念

1. 生命週期的完整順序(含 setup()

時間點 Options API Hook Composition API 等價 說明
1 beforeCreate 無直接等價(可用 setup() 前的程式碼) 元件實例已建立,但 datapropsmethods 尚未初始化。
2 setup() setup() 初始化 propsemit,建立 reactive state,返回要暴露給模板的值。
3 created onCreated(已廢除) → setup() 結束後,datacomputedwatch 已建立,可直接使用 this(僅 Options API)。
4 beforeMount onBeforeMount 模板已編譯,但尚未掛載到真實 DOM。
5 mounted onMounted 真實 DOM 已插入,這裡可以安全地操作 DOM、註冊第三方插件。
6 beforeUpdate onBeforeUpdate 响應式資料變更後,DOM 重新渲染前觸發。
7 updated onUpdated DOM 更新完畢。
8 beforeUnmount onBeforeUnmount 元件即將被銷毀,適合清理資源。
9 unmounted onUnmounted 元件已從 DOM 移除,最後的清理時機。

重點setup() 介於 beforeCreatecreated 之間,且在所有其他 hook 之前執行。這意味著在 setup() 內,你只能使用 propsemitexpose,而不能使用 this(因為實例尚未完成)。


2. setup() 前的 Hook:beforeCreate

beforeCreate 在 Vue 3 中仍然存在,但在 Composition API 中很少直接使用。若你仍需要在 setup() 前執行程式碼(例如全局的插件初始化),可以在 beforeCreate 中完成:

export default {
  beforeCreate() {
    console.log('元件即將建立,this.$options =', this.$options)
    // 例如:設定全域的錯誤處理器
    this.$options.errorHandler = (err, vm, info) => {
      console.error('全域錯誤', err, info)
    }
  }
}

注意:此時 props 尚未解析,datacomputed 也不存在,僅能存取靜態的 component options。


3. setup() 本身的特性

import { ref, computed, onMounted, onBeforeUnmount } from 'vue'

export default {
  // props 由外部傳入
  props: {
    msg: String,
    delay: { type: Number, default: 1000 }
  },

  // 這裡是 Composition API 的入口
  setup(props, { emit, expose }) {
    // 1️⃣ 只能透過參數取得 props,不能使用 this.msg
    const count = ref(0)

    // 2️⃣ computed 必須在 setup 內建立
    const doubled = computed(() => count.value * 2)

    // 3️⃣ 訂閱外部事件或啟動計時器,通常放在 onMounted
    const startTimer = () => {
      const timer = setInterval(() => {
        count.value++
        emit('update', count.value)   // 向父層傳遞變化
      }, props.delay)

      // onBeforeUnmount 時清除
      onBeforeUnmount(() => clearInterval(timer))
    }

    // 4️⃣ onMounted 只會在 DOM 完成掛載後執行
    onMounted(() => {
      console.log('DOM 已掛載,第一次 count =', count.value)
      startTimer()
    })

    // 5️⃣ expose 讓父層能直接呼叫子層的方法
    expose({
      reset() {
        count.value = 0
      }
    })

    // 返回給模板使用的資料
    return { count, doubled }
  }
}

重點說明

  • props 只能透過參數取得thissetup() 中是 undefined
  • 返回的物件會自動被 proxy 包裝,在模板中直接使用(如 {{ count }})。
  • onMountedonBeforeUnmount 等 hook 必須在 setup() 內呼叫,才能正確註冊。

4. setup() 後的 Hook:onMountedonBeforeUnmount

以下示範在 setup() 之後,如何利用 onMounted 取得真實 DOM,並在 onBeforeUnmount 釋放資源:

import { ref, onMounted, onBeforeUnmount } from 'vue'

export default {
  setup() {
    const inputEl = ref(null)   // 透過 template ref 取得 DOM

    const focusInput = () => {
      if (inputEl.value) inputEl.value.focus()
    }

    // 元件掛載完畢,執行一次自動聚焦
    onMounted(() => {
      console.log('元件已掛載')
      focusInput()
    })

    // 元件即將銷毀,移除事件監聽
    onBeforeUnmount(() => {
      console.log('元件即將被銷毀')
      // 假設我們在 onMounted 時加了 window 事件
      window.removeEventListener('resize', onResize)
    })

    // 只要在 setup 中返回,就能在模板使用
    return { inputEl, focusInput }
  },

  template: `
    <input ref="inputEl" @keyup.enter="focusInput" />
    <button @click="focusInput">Focus Input</button>
  `
}

5. 進階範例:watchwatchEffectonBeforeUpdate

import { ref, watch, watchEffect, onBeforeUpdate } from 'vue'

export default {
  props: { url: String },

  setup(props) {
    const data = ref(null)
    const loading = ref(false)

    // 1️⃣ 使用 watch 監聽 props.url 變化,重新抓取資料
    watch(
      () => props.url,
      async (newUrl, oldUrl) => {
        loading.value = true
        const res = await fetch(newUrl)
        data.value = await res.json()
        loading.value = false
      },
      { immediate: true }   // 立即執行一次
    )

    // 2️⃣ watchEffect 會在任何被依賴的響應式變數變化時重新執行
    watchEffect(() => {
      console.log('data 變化了:', data.value)
    })

    // 3️⃣ onBeforeUpdate 可用於偵測 DOM 重新渲染前的最後一次資料狀態
    onBeforeUpdate(() => {
      console.log('即將更新,當前 count =', data.value?.length)
    })

    return { data, loading }
  }
}

常見陷阱與最佳實踐

陷阱 說明 解決方式 / 最佳實踐
setup() 中使用 this this 此時指向 undefined,會拋出錯誤。 改用參數 propsemit,或 getCurrentInstance() 取得實例。
setup() 內直接呼叫 mounted 相關 API(如 document.querySelector DOM 尚未掛載,會得到 null 把 DOM 相關程式碼搬到 onMounted
忘記在 onBeforeUnmount 清除計時器或事件監聽 記憶體泄漏、意外觸發多次回呼。 始終 在相對應的「銷毀」hook 中釋放資源。
watch 內部使用非同步函式但未處理錯誤 錯誤會沉默,導致 UI 卡住。 使用 try / catch 或外層的錯誤捕捉機制。
過度在 setup() 中寫業務邏輯 使 setup() 變得龐大且難以測試。 把邏輯抽成 Composable(如 useFetchuseTimer),保持 setup() 輕量。
setup() 內返回非響應式值 雖然可以,但在模板中不會觸發更新。 若需要自動更新,使用 refreactivecomputed

最佳實踐總結

  1. 僅在 setup() 中建立 reactive state、computed、watch、hook 註冊
  2. 所有與 DOM 互動的程式碼放在 onMounted(或 onBeforeUpdate
  3. 資源釋放統一放在 onBeforeUnmount / onUnmounted
  4. 使用 composable 把可重用邏輯抽離,提升可測試性。
  5. 避免在 setup() 中使用 this,改以參數或 getCurrentInstance()

實際應用場景

場景 為何需要掌握 setup() 前後的 Hook 典型實作方式
表單驗證與自動聚焦 必須在 DOM 完成掛載後取得 <input> 元素,才能呼叫 .focus() setup() 中建立 ref,於 onMounted 呼叫 inputRef.value?.focus()
即時資料串流(WebSocket) 連線應在元件建立時就發起,但必須在元件銷毀時關閉。 setup() 中建立 socket,於 onMounted socket.connect(),於 onBeforeUnmount socket.disconnect()
路由參數變化重新抓取資料 props(如 route.params.id)變化時,需要重新發送請求。 使用 watch(() => props.id, fetchData, { immediate:true }),確保在 setup() 後即時觸發。
動畫與第三方 UI 套件 許多套件需要取得真實 DOM 才能初始化(如 Swiper、GSAP)。 setup() 中宣告 ref,於 onMounted 初始化套件;於 onBeforeUnmount 銷毀。
全域狀態同步(Pinia/Vuex) 需要在元件掛載時自動註冊 store,並在卸載時取消訂閱。 setup()const store = useStore()onMounted store.subscribe(...)onBeforeUnmount 取消。

總結

  • setup() 是 Vue 3 Composition API 的入口,位於 beforeCreatecreated 之間。
  • setup() 前只能取得靜態的 component options,無法操作 this、DOM 或 reactive 資料。
  • setup():建立 refreactivecomputedwatch,以及註冊生命週期 hook(onMountedonBeforeUnmount…)。
  • setup():利用 onMounted 取得真實 DOM、啟動第三方插件;利用 onBeforeUnmount/onUnmounted 釋放資源。
  • 常見陷阱包括誤用 this、提前操作 DOM、忘記清理副作用。遵守「建立 → 使用 → 清理」的三步走原則,可讓元件更安全、易維護。
  • 把可重用的邏輯抽成 Composable,讓 setup() 保持簡潔,提升測試與團隊協作效率。

掌握 setup() 前後的生命週期時序,你就能在 Vue 3 中寫出 結構清晰、效能佳、易於維護 的元件程式碼。祝開發順利!