Vue3 生命週期 – onRenderTracked / onRenderTriggered(除錯 Hook)
簡介
在 Vue 3 中,Composition API 讓我們可以更細緻地掌握元件的生命週期與渲染流程。除了常見的 onMounted、onUpdated、onUnmounted,Vue 也提供了兩個專門用於除錯的 hook:onRenderTracked 與 onRenderTriggered。
onRenderTracked會在 依賴追蹤(reactivity tracking)發生時被呼叫,讓你知道哪些響應式屬性被讀取。onRenderTriggered則在 觸發重新渲染(render triggered)時執行,告訴你是哪個依賴變動導致元件重新渲染。
這兩個 hook 雖然不會直接影響應用程式的行為,但在 效能優化、問題定位、學習 reactivity 原理 時非常有幫助。本文將深入說明它們的運作原理、使用方式,以及在實務開發中的最佳實踐。
核心概念
1. 為什麼需要除錯 Hook?
Vue 3 的響應式系統是基於 Proxy 與 依賴追蹤(dependency tracking)實作的。當一個組件渲染時,Vue 會自動收集所有在渲染過程中被「讀取」的響應式屬性,形成一個依賴集合。之後只要這些屬性其中之一發生變化,Vue 就會觸發重新渲染。
在大型專案中,不必要的重新渲染 常常是效能瓶頸的根源。利用 onRenderTracked 與 onRenderTriggered,我們可以:
- 看到 哪些屬性被追蹤(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為一個物件,包含type、target、key(若是get)以及oldValue(僅在has時)。 - 用途:檢視渲染過程中到底用了哪些資料。
注意:
onRenderTracked只在 渲染階段(即setup中返回的渲染函式或<template>)被觸發,不會在watchEffect、computed等其他 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包含變更類型(set、delete、add)以及變更前後的值。 - 用途:快速定位是哪個屬性改變了渲染,協助找出「不必要的」或「意外的」更新。
小提醒:若同一屬性在同一 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會追蹤firstName與lastName,因此onRenderTracked會分別收到兩個get事件。 - 點擊「改姓氏」時,
lastName的set事件會觸發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-for會 iterateitems,因此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>
觀察
- 初始渲染時,
show與message皆被追蹤。 - 點擊「切換顯示」只改變
show,onRenderTriggered只會報告show的set事件,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)收集依賴,不會對 watchEffect、computed 等其他追蹤機制產生事件。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 除錯 Hook 仍在生產環境執行 | 若忘記在 if (import.meta.env.DEV) 包裹,會在 production bundle 中留下額外的 console.log,影響效能與隱私。 |
務必以環境變數包裹,或在 vite.config.js 中設定 define: { __VUE_PROD_DEVTOOLS__: false }。 |
| 過度追蹤導致大量輸出 | 在大型元件裡一次渲染會產生上百筆 onRenderTracked 事件,訊息過多不易閱讀。 |
使用 console.group、console.filter,或只針對關鍵屬性使用 if (event.key === 'targetKey') 進行過濾。 |
與 watchEffect 混淆 |
初學者常以為 onRenderTracked 會捕捉所有 reactive 讀取,事實上僅限於渲染。 |
明確說明兩者差異,必要時在 watchEffect 內自行加入 console.log 觀察。 |
忽略 event.type |
只看 target 而不關注 type(get、has、iterate)會錯過依賴的細節。 |
在 log 中加入 event.type,協助判斷是屬性讀取還是集合遍歷。 |
忘記 key 於 v-for 中 |
v-for 若缺少 :key,渲染追蹤會變得模糊,onRenderTriggered 可能無法正確對應變更。 |
永遠在 v-for 中提供唯一且穩定的 key。 |
最佳實踐
- 僅在開發階段啟用:使用
import.meta.env.DEV包裝,或在vue.config.js中設定config.devtools = true。 - 聚焦重點屬性:如果你只想追蹤特定資料,於
onRenderTracked內做條件篩選,例如if (event.key === 'importantProp')。 - 結合 Performance 標記:使用
performance.mark/performance.measure搭配除錯 Hook,量化每次渲染耗時。 - 配合
vue-devtools:除onRenderTracked外,Vue Devtools 的「Component」面板也能顯示依賴圖,兩者互補。 - 寫測試時使用:在單元測試中可以把除錯 Hook 包成一個 helper,驗證特定屬性是否被追蹤或觸發,提升測試可讀性。
實際應用場景
1. 效能瓶頸排查
在一個大型儀表板(Dashboard)裡,某些卡片卡住不斷重新渲染。透過在問題卡片的根元件加入 onRenderTracked / onRenderTriggered,開發者可以快速看到是哪個全局狀態(例如 Vuex store 中的 userSettings)意外被讀取,進而使用 shallowRef 或 computed 重新設計,以降低不必要的渲染。
2. 教學與入門
對於剛學 Vue 3 的同學,這兩個 hook 是 實時觀察 reactivity 的好工具。配合簡單的範例(如上面的計數器),可以直觀感受到「讀取 → 追蹤 → 變更 → 觸發」的完整流程。
3. 自訂 Hook / 組件庫開發
在開發像是 UI 元件庫(例如 Element Plus)時,常需要保證每個元件只在需要時才重新渲染。開發者可以在元件的 setup 中加入除錯 Hook,確保 API 傳入的 props 或內部狀態不會產生過度追蹤。若發現 onRenderTracked 中頻繁出現不相關的屬性,則可以考慮使用 toRef、toRefs 或 watch 來細分依賴。
4. 錯誤排除 – 無法觸發更新
有時候 UI 更新卻沒有反映在畫面上,可能是因為依賴沒有被正確追蹤。使用 onRenderTracked 檢查是否真的讀取了預期的屬性,若沒有,則檢查是否在 setup 中錯誤地使用了普通變數或 Object.freeze 的資料。
總結
onRenderTracked與onRenderTriggered是 Vue 3 為 除錯 所提供的兩個生命週期 Hook,分別在 依賴追蹤 與 渲染觸發 時執行。- 透過這兩個 Hook,我們能夠 即時觀察 哪些響應式屬性被讀取、哪些變更導致重新渲染,從而找出效能瓶頸或不必要的更新。
- 使用時務必 限制在開發環境,並配合條件過濾、
key的正確使用,以免產生過多訊息或影響生產效能。 - 在實務開發、效能優化、教學或元件庫維護中,這兩個 Hook 都能提供關鍵資訊,幫助開發者更深入理解 Vue 3 的 reactivity 與 渲染機制。
最後提醒:除錯 Hook 雖然強大,但不應取代正式的效能分析工具(如 Chrome DevTools、Vue Devtools、Lighthouse)。結合多種工具,才能在大型應用中保持良好的效能與可維護性。祝你在 Vue 3 的開發旅程中玩得開心,渲染得更順暢!