阿里云主机折上折
  • 微信号
Current Site:Index > Caching of event handler functions

Caching of event handler functions

Author:Chuan Chen 阅读数:43796人阅读 分类: Vue.js

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:

  1. The function reference remains unchanged
  2. The component instance is the same
  3. 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:

  1. Define event handlers in setup or methods
  2. Avoid creating anonymous functions in render functions or templates
  3. For complex components, use useMemo or computed 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:

  1. When using dynamic components
  2. When forcibly remounting components
  3. When toggling components with v-if
  4. 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:

  1. Events are rebound during client-side hydration
  2. Event binding is not actually executed on the server
  3. 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:

  1. Use Vue Devtools to inspect event listeners
  2. Add logs in the patchEvent function
  3. 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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.