Responsive debugging (onTrack/onTrigger)
Responsive Debugging (onTrack/onTrigger)
Vue 3's reactivity system provides two powerful debugging hooks: onTrack
and onTrigger
. They allow developers to observe the collection and triggering of reactive dependencies, which is particularly useful for understanding complex reactive behaviors or troubleshooting performance issues.
Basic Concepts
onTrack
and onTrigger
are options for effect
that are called when reactive dependencies are tracked or triggered:
import { reactive, effect } from 'vue'
const state = reactive({ count: 0 })
effect(
() => {
console.log(state.count)
},
{
onTrack(e) {
console.log('Dependency tracked', e)
},
onTrigger(e) {
console.log('Dependency triggered', e)
}
}
)
onTrack Explained
When the effect runs for the first time and accesses a reactive property, onTrack
is called. It receives an event object containing the following key information:
effect
: The currently running effect functiontarget
: The reactive object being accessedtype
: The type of operation ('get' | 'has' | 'iterate')key
: The property key being accessed
const user = reactive({
name: 'Alice',
age: 25
})
effect(
() => {
const info = `${user.name} (${user.age})`
},
{
onTrack({ effect, target, type, key }) {
console.log(`Tracked ${type} operation on property ${String(key)}`)
}
}
)
// Output:
// Tracked get operation on property name
// Tracked get operation on property age
onTrigger Explained
When a reactive property is modified and triggers the effect to re-run, onTrigger
is called. The event object contains:
effect
: The triggered effecttarget
: The modified reactive objecttype
: The type of operation ('set' | 'add' | 'delete')key
: The modified property keynewValue
: The new valueoldValue
: The old value
const cart = reactive({
items: [],
total: 0
})
effect(
() => {
console.log('Cart total:', cart.total)
},
{
onTrigger({ key, newValue, oldValue }) {
console.log(`Total changed from ${oldValue} to ${newValue}`)
}
}
)
cart.total = 100
// Output:
// Total changed from 0 to 100
// Cart total: 100
Practical Use Cases
Debugging Computed Properties
import { computed, reactive } from 'vue'
const product = reactive({
price: 100,
quantity: 2
})
const total = computed({
get() {
return product.price * product.quantity
},
set(value) {
product.quantity = value / product.price
}
})
effect(
() => {
console.log('Total:', total.value)
},
{
onTrack(e) {
console.log('Computed property dependency tracked:', e.key)
},
onTrigger(e) {
console.log('Computed property update triggered:', e.key, e.newValue)
}
}
)
product.price = 200 // Will trigger onTrack and onTrigger
Observing Deep Reactive Objects
const nested = reactive({
user: {
profile: {
name: 'Bob',
preferences: {
theme: 'dark'
}
}
}
})
effect(
() => {
console.log(nested.user.profile.preferences.theme)
},
{
onTrack({ target, key }) {
console.log('Tracked path:', target, key)
}
}
)
nested.user.profile.preferences.theme = 'light'
// Will display the complete property access path
Performance Optimization Tips
Use onTrigger
to identify unnecessary effect triggers:
const config = reactive({
theme: 'dark',
version: '1.0.0' // This property won't change
})
effect(
() => {
// Only uses theme
document.body.className = config.theme
},
{
onTrigger({ key }) {
if (key === 'version') {
console.warn('version changed but not used, may cause unnecessary re-rendering')
}
}
}
)
config.version = '1.0.1' // Will trigger a warning
Using with watch
import { watch, reactive } from 'vue'
const state = reactive({
loading: false,
data: null
})
watch(
() => state.loading,
(newVal) => {
console.log('Loading state changed:', newVal)
},
{
onTrack({ target, key }) {
console.log('Watch dependency tracked:', key)
},
onTrigger({ key, oldValue, newValue }) {
console.log(`Watch triggered: ${key} changed from ${oldValue} to ${newValue}`)
}
}
)
state.loading = true
// Output:
// Watch dependency tracked: loading
// Watch triggered: loading changed from false to true
// Loading state changed: true
Debugging Component State
Using reactive debugging in components:
import { onRenderTracked, onRenderTriggered } from 'vue'
export default {
setup() {
onRenderTracked((e) => {
console.log('Render dependency tracked:', e)
})
onRenderTriggered((e) => {
console.log('Render triggered:', e)
})
return {
// Reactive data
}
}
}
Advanced Usage: Custom Debugging Tools
You can build reactive debugging tools based on these hooks:
let activeEffect = null
const effectStack = []
const dependencyMap = new WeakMap()
function trackEffect(effect, target, type, key) {
if (!dependencyMap.has(target)) {
dependencyMap.set(target, new Map())
}
const depsMap = dependencyMap.get(target)
if (!depsMap.has(key)) {
depsMap.set(key, new Set())
}
const dep = depsMap.get(key)
dep.add(effect)
}
function createEffect(fn, options = {}) {
const effect = () => {
try {
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
effect.options = options
effect()
return effect
}
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
if (activeEffect) {
trackEffect(activeEffect, target, 'get', key)
activeEffect.options.onTrack?.({
effect: activeEffect,
target,
type: 'get',
key
})
}
return target[key]
},
set(target, key, value) {
const oldValue = target[key]
target[key] = value
const depsMap = dependencyMap.get(target)
if (depsMap && depsMap.has(key)) {
depsMap.get(key).forEach((effect) => {
effect.options.onTrigger?.({
effect,
target,
type: 'set',
key,
newValue: value,
oldValue
})
effect()
})
}
return true
}
})
}
Limitations of Reactive Debugging
- These debugging hooks should be disabled in production as they impact performance.
- For large applications, they can generate excessive debug logs.
- Cannot directly debug the internal reactivity of state management systems like Vuex/Pinia.
Integration with Other Debugging Tools
Can be used alongside Vue Devtools:
// Register global debugging hooks in development environment
if (process.env.NODE_ENV === 'development') {
let isDebugging = false
const toggleDebug = () => {
isDebugging = !isDebugging
if (isDebugging) {
window.__VUE_DEBUG_HOOKS__ = {
onTrack: ({ key }) => console.log('Global tracking:', key),
onTrigger: ({ key }) => console.log('Global triggering:', key)
}
} else {
delete window.__VUE_DEBUG_HOOKS__
}
}
// Can be called via console with toggleDebug()
window.toggleVueDebug = toggleDebug
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn