阿里云主机折上折
  • 微信号
Current Site:Index > The internal mechanism of the component event system

The internal mechanism of the component event system

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

Internal Mechanism of the Component Event System

Vue 3's component event system is built on the publish-subscribe pattern, where custom events are triggered via the emit method, and parent components listen using the v-on or @ syntax. Behind this mechanism lies a sophisticated design, encompassing the complete chain of event registration, dispatch, and callback handling.

Event Registration and Dispatch Process

When a component instance is created, the event system is initialized, with core logic located in componentEmits.ts. Each component maintains an emits object that stores all registered event handlers:

// Simplified component instance structure
interface ComponentInternalInstance {
  emits: Record<string, Array<Function>> | null
  props: Record<string, any>
}

When a parent component uses @event="handler", Vue compiles it into an onEvent property in the props. For example:

<Child @custom-event="handleEvent" />

The compiled runtime code is equivalent to:

h(Child, {
  onCustomEvent: handleEvent
})

Implementation Principle of the emit Method

The emit method inside a component does not directly trigger events but processes them through the following steps:

  1. Convert the event name: myEventonMyEvent
  2. Look up the corresponding handler in the props
  3. Execute the handler and pass arguments

Key implementation in the source code:

function emit(instance, event: string, ...args: any[]) {
  const props = instance.vnode.props || EMPTY_OBJ
  let handler = props[toHandlerKey(event)]
  
  if (!handler && event.indexOf('update:') === 0) {
    handler = props[toHandlerKey(camelize(event))]
  }
  
  if (handler) {
    callWithAsyncErrorHandling(
      handler,
      instance,
      ErrorCodes.COMPONENT_EVENT_HANDLER,
      args
    )
  }
}

Underlying Handling of Event Modifiers

Vue provides modifiers like .once and .passive for the event system, which are converted into corresponding DOM event options during the compilation phase:

<button @click.once="doThis"></button>

The compiled code adds an _once flag:

_createVNode(button, {
  onClick: {
    handler: doThis,
    once: true
  }
})

The event handler execution logic checks these flags:

function invoker(e: Event) {
  if (once) {
    remove(event, invoker, undefined, true)
  }
  callWithAsyncErrorHandling(handler, args)
}

Custom Events vs. Native Event Dispatch

Vue 3 distinguishes between native DOM events and component custom events via event names. For example, with onClick:

if (event.startsWith('on') && !isReservedProp(event)) {
  // Handle custom events
  const eventName = event[2].toLowerCase() + event.slice(3)
  if (!instance.emitsOptions || hasOwn(instance.emitsOptions, eventName)) {
    // Component custom event logic
  } else {
    // Native DOM event logic
  }
}

Event Validation Mechanism

In development mode, Vue validates the configuration of the emits option:

const Comp = {
  emits: {
    submit: (payload: { email: string, password: string }) => {
      return payload.email.includes('@') && payload.password.length > 0
    }
  }
}

If validation fails, a warning is logged to the console. The core validation logic:

function validateEvent(
  event: string,
  args: unknown[],
  emitsOptions: ObjectEmitsOptions | null
): boolean {
  if (emitsOptions) {
    const validator = emitsOptions[event]
    if (isFunction(validator)) {
      return validator(...args)
    }
  }
  return true
}

Interaction Mechanism with v-model

v-model is essentially syntactic sugar, relying on the event system under the hood. For example:

<input v-model="text" />
<!-- Equivalent to -->
<input 
  :value="text"
  @input="text = $event.target.value"
/>

For custom components, v-model is converted into a modelValue property and an update:modelValue event:

// Compiler transformation logic
if (dir.name === 'model') {
  props.push(
    createObjectProperty(`modelValue`, dir.exp),
    createObjectProperty(`onUpdate:modelValue`, 
      createSimpleExpression(`$event => ${genAssignmentCode(dir.exp, '$event')}`, false)
    )
  )
}

Event Caching Optimization Strategy

Vue 3 caches event handlers to avoid unnecessary updates. During each render, it compares old and new event handlers:

function patchEvent(
  el: Element,
  rawName: string,
  prevValue: EventValue | null,
  nextValue: EventValue | null
) {
  const invoker = prevValue && prevValue.invoker
  if (nextValue && invoker) {
    // Reuse existing invoker
    invoker.value = nextValue
  } else {
    // Create new invoker
    const name = rawName.slice(2).toLowerCase()
    const invoker = createInvoker(nextValue)
    el.addEventListener(name, invoker)
  }
}

Cross-Component Event Propagation Pattern

Cross-level event propagation can be achieved via provide/inject, similar to React's Context:

// Ancestor component
provide('eventBus', {
  emit: (event) => instance.emit(event)
})

// Descendant component
const bus = inject('eventBus')
bus.emit('custom-event')

This pattern is commonly used in component libraries for global notifications, though dedicated libraries like mitt are recommended.

Performance Optimization Designs

The event system includes several performance optimizations:

  1. Lazy Event Registration: DOM event listeners are added only on first trigger.
  2. Event Delegation: A single top-level listener is used for similar events.
  3. Bitwise Flags: Binary bits store event states.

For example, bitwise operations determine whether an event needs updating:

const shouldUpdate = 
  (patchFlag & PatchFlags.PROPS) &&
  !(patchFlag & PatchFlags.HYDRATE_EVENTS)

Integration with the Composition API

In the setup function, the event system can be accessed via context.emit:

setup(props, { emit }) {
  const handleClick = () => {
    emit('click', { position: 'header' })
  }
  
  return { handleClick }
}

The compiler converts @click in the template into an emit call in the setup context during the code generation phase:

// Compiled output
_createVNode(button, {
  onClick: _ctx[0] || (_ctx[0] = $event => emit('click', $event))
})

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.