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.
Injection of
setupContext
: In Vue components, thesetup
function receives two parameters:props
andcontext
.context
includes a series of properties and methods, one of which is theemit
function. In Vue 3 components, thesetup
function returns an object, and you can add theemit
function tosetupContext
so that users can access it within the component usingsetupContext.emit
.Here’s a simple example demonstrating how to add
emit
tosetupContext
in thesetup
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 usingsetupContext.emit
.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, thechange
event becomesonChange
. 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
becomesonChange
. It then looks for the corresponding event handling function ininstance.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
.