Caching of event handler functions
Event Handler Caching
Vue3 optimizes event handling by caching event handlers to reduce unnecessary re-renders. When a component re-renders, if the event handler remains unchanged, Vue will reuse the previously created function instance. This mechanism significantly improves performance in scenarios with frequent updates.
const MyComponent = {
setup() {
const count = ref(0)
// This function will be cached
const increment = () => {
count.value++
}
return {
count,
increment
}
},
template: `
<button @click="increment">
{{ count }}
</button>
`
}
Caching Mechanism Implementation Principle
Vue3 handles event handlers specially through the patchProp
function when processing DOM properties. In runtime-dom/src/patchProp.ts
, event handlers are wrapped in a higher-order function:
export function patchEvent(
el: Element,
rawName: string,
prevValue: any,
nextValue: any,
instance: ComponentInternalInstance | null = null
) {
// Handle event binding logic
const invoker = el._vei || (el._vei = {})
const existingInvoker = invoker[rawName]
if (nextValue) {
if (existingInvoker) {
// Update existing invoker
existingInvoker.value = nextValue
} else {
// Create new invoker
const name = rawName.slice(2).toLowerCase()
const invoker = (invoker[rawName] = createInvoker(nextValue, instance))
el.addEventListener(name, invoker)
}
} else if (existingInvoker) {
// Remove event listener
el.removeEventListener(name, existingInvoker)
invoker[rawName] = undefined
}
}
Caching Trigger Conditions
Event handler caching requires the following conditions:
- The function reference remains unchanged
- The component instance is the same
- The event type is the same
When these conditions are met, Vue will reuse the previously created event listener. For example, rebinding will not occur in the following case:
const App = {
setup() {
const handler = () => console.log('clicked')
return { handler }
},
template: `<button @click="handler">Click</button>`
}
Dynamic Event Name Handling
For dynamic event names, Vue3 performs special handling:
const App = {
setup() {
const eventName = ref('click')
const handler = () => console.log('dynamic event')
setTimeout(() => {
eventName.value = 'dblclick'
}, 1000)
return { eventName, handler }
},
template: `<button @[eventName]="handler">Click</button>`
}
In this case, Vue will first remove the old event listener and then add a new one, but the handler itself will still be cached.
Special Case for Inline Handlers
Inline functions written directly in templates are not cached:
const App = {
template: `
<button @click="() => console.log('inline')">
Inline Handler
</button>
`
}
A new function instance is created on each re-render. In such cases, avoid using inline functions in frequently updated components.
Custom Event Caching
Custom events also benefit from the caching mechanism:
const Child = {
emits: ['custom'],
setup(props, { emit }) {
const emitEvent = () => emit('custom', 'data')
return { emitEvent }
},
template: `<button @click="emitEvent">Emit</button>`
}
const Parent = {
components: { Child },
setup() {
const handler = (data) => console.log(data)
return { handler }
},
template: `<Child @custom="handler" />`
}
Performance Optimization Practices
To fully leverage the event caching mechanism, follow these practices:
- Define event handlers in
setup
ormethods
- Avoid creating anonymous functions in render functions or templates
- For complex components, use
useMemo
orcomputed
to optimize event handlers
const ComplexComponent = {
setup() {
const data = ref([])
const filter = ref('')
// Optimized handler
const filteredHandler = computed(() => {
return () => {
console.log(data.value.filter(item => item.includes(filter.value)))
}
})
return {
data,
filter,
filteredHandler
}
}
}
Comparison with Vue2
In Vue2, event listeners are recreated on every update:
// Vue2 example
new Vue({
methods: {
handleClick() {
console.log('clicked')
}
},
template: `<button @click="handleClick">Click</button>`
})
Although methods are defined in methods
, events are rebound on every update. Vue3 improves this by updating event listeners only when necessary.
Key Functions in the Source Code
In runtime-core/src/componentRenderUtils.ts
, the renderProps
function handles event binding:
function renderProps(
instance: ComponentInternalInstance,
props: Data,
isSVG: boolean
) {
const { attrs, props: propsOptions } = instance
for (const key in props) {
if (isReservedProp(key)) continue
const value = props[key]
if (isOn(key)) {
// Handle events
patchEvent(
instance.vnode.el as Element,
key,
null,
value,
instance
)
} else {
// Handle regular attributes
patchAttr(
instance.vnode.el as Element,
key,
null,
value,
isSVG
)
}
}
}
Cache Invalidation Scenarios
Caching may be invalidated in certain cases:
- When using dynamic components
- When forcibly remounting components
- When toggling components with
v-if
- When modifying the reference of the event handler
const App = {
setup() {
const handler = () => console.log('original')
const changeHandler = () => {
handler = () => console.log('changed') // This will trigger rebinding
}
return { handler, changeHandler }
},
template: `
<button @click="handler">Click</button>
<button @click="changeHandler">Change Handler</button>
`
}
Interaction with Composition API
In the Composition API, event handlers created with ref
and reactive
are automatically cached:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
// This function will be cached
const increment = () => {
count.value++
}
return {
count,
double,
increment
}
}
}
Behavior in Server-Side Rendering
In SSR scenarios, the event handler caching mechanism remains effective but with the following differences:
- Events are rebound during client-side hydration
- Event binding is not actually executed on the server
- The hydration process reuses server-generated markup
// Server-side component
export default {
setup() {
const clickHandler = () => {
// This function won't be called on the server
console.log('Client only')
}
return { clickHandler }
},
template: `<button @click="clickHandler">SSR Button</button>`
}
Debugging Caching Behavior
You can debug event caching in the following ways:
- Use Vue Devtools to inspect event listeners
- Add logs in the
patchEvent
function - Compare function references before and after rendering
// Custom patchEvent for debugging
const originalPatchEvent = Vue.__patchEvent
Vue.__patchEvent = function(...args) {
console.log('Patching event:', args)
return originalPatchEvent.apply(this, args)
}
Comparison with Other Frameworks
React has similar optimizations but implements them differently:
// React example
function ReactComponent() {
const handleClick = useCallback(() => {
console.log('Memoized handler')
}, [])
return <button onClick={handleClick}>Click</button>
}
Vue3's caching is automatic, while React requires explicit use of useCallback
. Angular optimizes event binding automatically through its change detection mechanism.
Advanced Cache Control
For cases requiring fine-grained control, you can use markRaw
to mark objects:
import { markRaw } from 'vue'
const rawObject = markRaw({
handler: () => console.log('raw handler')
})
export default {
setup() {
return {
rawObject
}
},
template: `<button @click="rawObject.handler">Raw</button>`
}
This ensures the event handler is not recreated even when the component re-renders.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:虚拟DOM的静态标记
下一篇:插槽内容的稳定化处理