本文 AI 產出,尚未審核

Vue3 生命週期 – onRenderTracked / onRenderTriggered(除錯 Hook)


簡介

在 Vue 3 中,Composition API 讓我們可以更細緻地掌握元件的生命週期與渲染流程。除了常見的 onMountedonUpdatedonUnmounted,Vue 也提供了兩個專門用於除錯的 hook:onRenderTrackedonRenderTriggered

  • onRenderTracked 會在 依賴追蹤(reactivity tracking)發生時被呼叫,讓你知道哪些響應式屬性被讀取。
  • onRenderTriggered 則在 觸發重新渲染(render triggered)時執行,告訴你是哪個依賴變動導致元件重新渲染。

這兩個 hook 雖然不會直接影響應用程式的行為,但在 效能優化問題定位學習 reactivity 原理 時非常有幫助。本文將深入說明它們的運作原理、使用方式,以及在實務開發中的最佳實踐。


核心概念

1. 為什麼需要除錯 Hook?

Vue 3 的響應式系統是基於 Proxy依賴追蹤(dependency tracking)實作的。當一個組件渲染時,Vue 會自動收集所有在渲染過程中被「讀取」的響應式屬性,形成一個依賴集合。之後只要這些屬性其中之一發生變化,Vue 就會觸發重新渲染。

在大型專案中,不必要的重新渲染 常常是效能瓶頸的根源。利用 onRenderTrackedonRenderTriggered,我們可以:

  • 看到 哪些屬性被追蹤(tracked)。
  • 了解 哪些變更真正觸發了渲染(triggered)。
  • 判斷 是否有過度追蹤或不必要的依賴。

小技巧:在開發環境把這兩個 hook 包在 if (import.meta.env.DEV) 裡,避免在生產環境產生額外開銷。


2. onRenderTracked 的工作原理

import { onRenderTracked, getCurrentInstance } from 'vue'

onRenderTracked((event) => {
  // event.type: 'get' | 'has' | 'iterate'
  // event.target: 被追蹤的 reactive object
  // event.key: 觸發追蹤的屬性 key(若有)
  console.log('[Tracked]', event)
})
  • 觸發時機:每當渲染函式(render)讀取一個響應式屬性,Vue 會呼叫此 hook。
  • 參數說明event 為一個物件,包含 typetargetkey(若是 get)以及 oldValue(僅在 has 時)。
  • 用途:檢視渲染過程中到底用了哪些資料。

注意onRenderTracked 只在 渲染階段(即 setup 中返回的渲染函式或 <template>)被觸發,不會watchEffectcomputed 等其他 reactive 追蹤中觸發。


3. onRenderTriggered 的工作原理

import { onRenderTriggered } from 'vue'

onRenderTriggered((event) => {
  // event.type: 'set' | 'delete' | 'add'
  // event.target: 被觸發的 reactive object
  // event.key: 變更的屬性 key
  // event.newValue / event.oldValue: 變更前後的值
  console.log('[Triggered]', event)
})
  • 觸發時機:當任何已被追蹤的屬性發生變化,且這個變化導致元件 重新渲染 時,Vue 會呼叫此 hook。
  • 參數說明event 包含變更類型(setdeleteadd)以及變更前後的值。
  • 用途:快速定位是哪個屬性改變了渲染,協助找出「不必要的」或「意外的」更新。

小提醒:若同一屬性在同一 tick 中被多次變更,只會觸發一次 onRenderTriggered(因為 Vue 會合併更新)。


4. 使用範例

以下範例示範在一個簡單的計數器元件中同時使用兩個除錯 hook,並說明它們的輸出。

範例 1:基本計數器

<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue'

const count = ref(0)

// 只在開發環境啟用除錯 hook
if (import.meta.env.DEV) {
  onRenderTracked((e) => {
    console.log('%c[Render Tracked]', 'color:#42b983', e)
  })
  onRenderTriggered((e) => {
    console.log('%c[Render Triggered]', 'color:#ff5252', e)
  })
}

function increment() {
  count.value++
}
</script>

<template>
  <div>
    <p>目前計數:{{ count }}</p>
    <button @click="increment">+1</button>
  </div>
</template>

輸出說明

  • 點擊 +1 時,count.value讀取onRenderTracked)以及 設定onRenderTriggered)。
  • 若在同一 tick 中多次點擊,onRenderTracked 仍會在每次渲染時觸發,而 onRenderTriggered 只會在最後一次更新時觸發一次(取決於 Vue 的批次更新機制)。

範例 2:使用 computed 與多個 reactive

<script setup>
import { ref, computed, onRenderTracked, onRenderTriggered } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed(() => `${firstName.value} ${lastName.value}`)

if (import.meta.env.DEV) {
  onRenderTracked((e) => console.log('[Tracked]', e))
  onRenderTriggered((e) => console.log('[Triggered]', e))
}

function changeLastName() {
  lastName.value = 'Smith'
}
</script>

<template>
  <div>
    <p>全名:{{ fullName }}</p>
    <button @click="changeLastName">改姓氏</button>
  </div>
</template>

觀察結果

  • 初始渲染時,fullName 會追蹤 firstNamelastName,因此 onRenderTracked 會分別收到兩個 get 事件。
  • 點擊「改姓氏」時,lastNameset 事件會觸發 onRenderTriggered,並導致 fullName 重新計算與渲染。

範例 3:列表渲染與 v-for 的追蹤

<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue'

const items = ref([
  { id: 1, text: 'Apple' },
  { id: 2, text: 'Banana' },
  { id: 3, text: 'Cherry' },
])

if (import.meta.env.DEV) {
  onRenderTracked((e) => console.log('[Tracked]', e))
  onRenderTriggered((e) => console.log('[Triggered]', e))
}

function addItem() {
  items.value.push({ id: Date.now(), text: 'New Fruit' })
}
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id">{{ item.text }}</li>
  </ul>
  <button @click="addItem">新增項目</button>
</template>

關鍵點

  • v-foriterate items,因此 onRenderTracked 會收到 iterate 事件。
  • addItem 呼叫 push,觸發 add 事件,onRenderTriggered 會顯示是哪個屬性(items)被改變,導致列表重新渲染。

範例 4:條件渲染與 v-if 的影響

<script setup>
import { ref, onRenderTracked, onRenderTriggered } from 'vue'

const show = ref(true)
const message = ref('Hello Vue 3!')

if (import.meta.env.DEV) {
  onRenderTracked((e) => console.log('[Tracked]', e))
  onRenderTriggered((e) => console.log('[Triggered]', e))
}

function toggle() {
  show.value = !show.value
}
</script>

<template>
  <button @click="toggle">切換顯示</button>
  <p v-if="show">{{ message }}</p>
</template>

觀察

  • 初始渲染時,showmessage 皆被追蹤。
  • 點擊「切換顯示」只改變 showonRenderTriggered 只會報告 showset 事件,message 不會被觸發,證明條件渲染能有效減少不必要的更新。

範例 5:結合 watchEffect(不會觸發除錯 hook)

<script setup>
import { ref, watchEffect, onRenderTracked } from 'vue'

const count = ref(0)

watchEffect(() => {
  console.log('watchEffect:', count.value)
})

// 即使在 watchEffect 中讀取 count,onRenderTracked 不會收到事件
if (import.meta.env.DEV) {
  onRenderTracked((e) => console.log('[Tracked]', e))
}
</script>

<template>
  <button @click="count++">+1</button>
  <p>Count: {{ count }}</p>
</template>

說明:此例說明 onRenderTracked 僅針對 渲染階段(template / render function)收集依賴,不會watchEffectcomputed 等其他追蹤機制產生事件。


常見陷阱與最佳實踐

陷阱 說明 解決方式
除錯 Hook 仍在生產環境執行 若忘記在 if (import.meta.env.DEV) 包裹,會在 production bundle 中留下額外的 console.log,影響效能與隱私。 務必以環境變數包裹,或在 vite.config.js 中設定 define: { __VUE_PROD_DEVTOOLS__: false }
過度追蹤導致大量輸出 在大型元件裡一次渲染會產生上百筆 onRenderTracked 事件,訊息過多不易閱讀。 使用 console.groupconsole.filter,或只針對關鍵屬性使用 if (event.key === 'targetKey') 進行過濾。
watchEffect 混淆 初學者常以為 onRenderTracked 會捕捉所有 reactive 讀取,事實上僅限於渲染。 明確說明兩者差異,必要時在 watchEffect 內自行加入 console.log 觀察。
忽略 event.type 只看 target 而不關注 typegethasiterate)會錯過依賴的細節。 在 log 中加入 event.type,協助判斷是屬性讀取還是集合遍歷。
忘記 keyv-for v-for 若缺少 :key,渲染追蹤會變得模糊,onRenderTriggered 可能無法正確對應變更。 永遠v-for 中提供唯一且穩定的 key

最佳實踐

  1. 僅在開發階段啟用:使用 import.meta.env.DEV 包裝,或在 vue.config.js 中設定 config.devtools = true
  2. 聚焦重點屬性:如果你只想追蹤特定資料,於 onRenderTracked 內做條件篩選,例如 if (event.key === 'importantProp')
  3. 結合 Performance 標記:使用 performance.mark / performance.measure 搭配除錯 Hook,量化每次渲染耗時。
  4. 配合 vue-devtools:除 onRenderTracked 外,Vue Devtools 的「Component」面板也能顯示依賴圖,兩者互補。
  5. 寫測試時使用:在單元測試中可以把除錯 Hook 包成一個 helper,驗證特定屬性是否被追蹤或觸發,提升測試可讀性。

實際應用場景

1. 效能瓶頸排查

在一個大型儀表板(Dashboard)裡,某些卡片卡住不斷重新渲染。透過在問題卡片的根元件加入 onRenderTracked / onRenderTriggered,開發者可以快速看到是哪個全局狀態(例如 Vuex store 中的 userSettings)意外被讀取,進而使用 shallowRefcomputed 重新設計,以降低不必要的渲染。

2. 教學與入門

對於剛學 Vue 3 的同學,這兩個 hook 是 實時觀察 reactivity 的好工具。配合簡單的範例(如上面的計數器),可以直觀感受到「讀取 → 追蹤 → 變更 → 觸發」的完整流程。

3. 自訂 Hook / 組件庫開發

在開發像是 UI 元件庫(例如 Element Plus)時,常需要保證每個元件只在需要時才重新渲染。開發者可以在元件的 setup 中加入除錯 Hook,確保 API 傳入的 props 或內部狀態不會產生過度追蹤。若發現 onRenderTracked 中頻繁出現不相關的屬性,則可以考慮使用 toReftoRefswatch 來細分依賴。

4. 錯誤排除 – 無法觸發更新

有時候 UI 更新卻沒有反映在畫面上,可能是因為依賴沒有被正確追蹤。使用 onRenderTracked 檢查是否真的讀取了預期的屬性,若沒有,則檢查是否在 setup 中錯誤地使用了普通變數或 Object.freeze 的資料。


總結

  • onRenderTrackedonRenderTriggered 是 Vue 3 為 除錯 所提供的兩個生命週期 Hook,分別在 依賴追蹤渲染觸發 時執行。
  • 透過這兩個 Hook,我們能夠 即時觀察 哪些響應式屬性被讀取、哪些變更導致重新渲染,從而找出效能瓶頸或不必要的更新。
  • 使用時務必 限制在開發環境,並配合條件過濾、key 的正確使用,以免產生過多訊息或影響生產效能。
  • 在實務開發、效能優化、教學或元件庫維護中,這兩個 Hook 都能提供關鍵資訊,幫助開發者更深入理解 Vue 3 的 reactivity渲染機制

最後提醒:除錯 Hook 雖然強大,但不應取代正式的效能分析工具(如 Chrome DevTools、Vue Devtools、Lighthouse)。結合多種工具,才能在大型應用中保持良好的效能與可維護性。祝你在 Vue 3 的開發旅程中玩得開心,渲染得更順暢!