If you have any thoughts on my blog or articles and you want to let me know, you can either post a comment below(public) or tell me via this i_kkkp@163.com

Implementation of Vue.js Component Events and Emit

Introduction

To explain the implementation of Vue.js component events and emit, it’s essential to first understand the application of component events and emit. This will provide a clearer understanding of how component events and emit are implemented.

For component events, we’ve previously discussed the browser’s event mechanism:

Browser Event Review

Browser events refer to various signals of interaction and state changes that occur on a web page. These events can be triggered by user actions, changes in the browser’s state, or other factors. Here are some common DOM events and their descriptions:

Mouse Events:

  • click: Triggered when the mouse clicks on an element. For touch-screen devices, it’s triggered when there’s a tap on the screen.
  • contextmenu: Triggered when the right mouse button clicks on an element.
  • mouseover / mouseout: Triggered when the mouse pointer enters or leaves an element.
  • mousedown / mouseup: Triggered when the mouse button is pressed or released over an element.
  • mousemove: Triggered when the mouse is moved.

Keyboard Events:

  • keydown and keyup: Triggered when a key is pressed down or released.

Form Element Events:

  • submit: Triggered when a visitor submits a <form>.
  • focus: Triggered when a visitor focuses on an element, such as an <input>.

Document Events:

  • DOMContentLoaded: Triggered when HTML is fully loaded and processed, and the DOM is completely constructed.

CSS Events:

  • transitionend: Triggered when a CSS animation is completed.

These events can be captured and processed using event listeners in JavaScript. By adding event listeners to elements, specific code can be executed when certain events occur. Events form the foundation of interaction and responsiveness in web development.

Now, how does component events differ from browser events?

In the context of events, we can understand them as specific occurrences at a given point in time, signaling that something has happened. An event is a signal that can be received and processed, and that’s what events are.

In the case of component events, it refers to the communication between Vue components, achieved through events. In this context, events serve as a means of communication between components, and this communication is implemented using events. Here, events specifically refer to what we call component events.

The primary mechanism for communication between Vue components using events is through the use of the emit and on methods. When a child component needs to communicate with a parent component, it triggers an event and uses the emit method to send the event. The parent component, in turn, uses the v-on directive in the template to listen for this event and execute the corresponding logic.

<!-- Child Component ChildComponent.vue -->
<template>
  <button @click="sendMessage">Notify Parent Component</button>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      // Use emit to send a custom event
      this.$emit('child-event', 'Hello from child!');
    }
  }
}
</script>
<!-- Parent Component ParentComponent.vue -->
<template>
  <div>
    <child-component @child-event="handleChildEvent"></child-component>
    <p>Message received from child: {{ messageFromChild }}</p>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      messageFromChild: ''
    };
  },
  methods: {
    handleChildEvent(message) {
      // Listen to the child component's event using v-on
      this.messageFromChild = message;
    }
  }
}
</script>

Vue’s event mechanism is built on the publish/subscribe pattern. $emit is used to publish (trigger) an event, while v-on is used to subscribe (listen) to events. This enables decoupling between different components.

Firstly, we notice that the child component uses this.$emit('child-event', 'Hello from child!') to send a custom event named ‘child-event’. The parent component, on the other hand, listens to this event using @child-event="handleChildEvent". Here, ‘child-event’ is the custom event name, and ‘handleChildEvent’ is a method defined in the parent component to handle the message sent by the child component.

When using a custom MyComponent component, we can listen to the emitted custom event using the v-on directive:

You can see that the custom event ‘change’ is compiled into a property named ‘onChange’ and stored in the props data object. This is essentially a convention. As a framework designer, you can design the compilation result of events according to your expectations. In the actual implementation, emitting a custom event boils down to finding the corresponding event handling function in the props data object based on the event name, as shown in the following code:

Implementation of emit

// Definition of MyComponent component
const MyComponent = {
  name: 'MyComponent',
  setup(props, { emit }) {
    // Emit the 'change' event and pass two parameters to the event handler
    emit('change', 1, 2);

    return () => {
      // Render logic of the component
      return /* ... */;
    };
  }
};

The above code will be compiled into a node CompNode, with the type being the MyComponent component. The emit function is passed to the setup function of the component, as shown below:

// Definition of a Vue component node CompNode
const CompNode = {
  // Specify the type of the node as the MyComponent component
  type: MyComponent,

  // Props object passed to the component
  props: {
    // In the MyComponent component, there will be an onChange event handling function, whose value is handler
    onChange: handler
  }
}; 

As you can see, the custom event ‘change’ is compiled into a property named ‘onChange’ and stored in the props data object. This is essentially a convention. As a framework designer, you can design the compilation result of events according to your expectations.

In the actual implementation, emitting a custom event essentially involves finding the corresponding event handling function in the props data object based on the event name, as shown in the following code:

// Mounting the component
function mountComponent(vnode, container, anchor) {
  // Omitted code

  const instance = {
    state, // State
    props: shallowReactive(props), // Reactive handling of props
    isMounted: false,
    subTree: null
  };

  // Define the emit function, which takes two parameters
  // event: Event name
  // payload: Parameters passed to the event handling function
  function emit(event, ...payload) {
    // Process the event name according to the convention, e.g., change --> onChange
    const eventName = `on${event[0].toUpperCase()}${event.slice(1)}`;

    // Find the corresponding event handling function in props based on the processed event name
    const handler = instance.props[eventName];

    if (handler) {
      // Call the event handling function and pass the parameters
      handler(...payload);
    } else {
      console.error('Event does not exist');
    }
  }

  // Add the emit function to setupContext
  const setupContext = { attrs, emit };

  // Omitted code
}

// Function to parse props data, with special handling for event-type props
function resolveProps(options, propsData) {
  const props = {};
  const attrs = {};

  for (const key in propsData) {
    // Add props starting with 'on' as strings to props data, otherwise add to attrs
    if (key in options || key.startsWith('on')) {
      props[key] = propsData[key];
    } else {
      attrs[key] = propsData[key];
    }
  }

  return [props, attrs];
}

The implementation principle of emit involves two aspects: the injection of setupContext and the convention-based transformation of event names.

  1. Injection of setupContext: In Vue components, the setup function receives two parameters: props and context. context includes a series of properties and methods, one of which is the emit function. In Vue 3 components, the setup function returns an object, and you can add the emit function to setupContext so that users can access it within the component using setupContext.emit.

    Here’s a simple example demonstrating how to add emit to setupContext in the setup function:

    setup(props, context) {
      // Add emit to setupContext
      context.emit = emit;
    
      // Other setup logic
      // ...
    
      // Return the object returned by setup
      return {};
    }

    This way, users can call the emit function within the component using setupContext.emit.

  2. Convention-based transformation of event names: Inside the emit function, to match event handling functions in the component template, event names need to be conventionally transformed. Vue uses a convention of converting event names to camelCase. For example, the change event becomes onChange. This allows users to listen to events in the component template using camelCase.

    Here’s a simple example illustrating the convention-based transformation of event names:

    function emit(event, ...payload) {
      // Process the event name according to the convention, e.g., change --> onChange
      const eventName = `on${event[0].toUpperCase()}${event.slice(1)}`;
    
      // Find the corresponding event handling function in props based on the processed event name
      const handler = instance.props[eventName];
    
      if (handler) {
        // Call the event handling function and pass the parameters
        handler(...payload);
      } else {
        console.error('Event does not exist');
      }
    }

    In this example, the emit function transforms the event name into camelCase with “on” at the beginning. For instance, change becomes onChange. It then looks for the corresponding event handling function in instance.props and executes it.

It’s important to note that event-type props are not found in instance.props, so they are stored in attrs. To address this, when parsing props data, event-type props are specially handled to ensure they are correctly added to props instead of attrs. This allows the emit function to correctly find event handling functions in instance.props.

Getting Started with React - Part 1 Implementation Principles of Vue.js Components

Comments