Introduction
In this section, we will discuss how to handle events in Vue, including how to describe events in virtual nodes, how to add events to DOM elements, and how to update events. Let’s start by addressing the first question, which is how to describe events in virtual nodes. Events can be considered as special attributes, so we can agree that any attribute starting with the string “on” in the vnode.props object should be treated as an event. For example:
const vnode = {
type: 'p',
props: {
// Describe events using onXxx
onClick: () => {
alert('clicked');
}
},
children: 'text'
};
Once we have resolved how events are described in virtual nodes, let’s see how to add events to DOM elements. This is very simple, just call the addEventListener
function in the patchProps
method to bind the event, as shown in the following code:
function patchProps(el, key, prevValue, nextValue) {
// Match attributes starting with on as events
if (/^on/.test(key)) {
// Get the corresponding event name based on the attribute name, e.g., onClick ---> click
const name = key.slice(2).toLowerCase();
// Remove the previously bound event handler
prevValue && el.removeEventListener(name, prevValue);
// Bind the new event handler
el.addEventListener(name, nextValue);
} else if (key === 'class') {
// Omitted code (handling class attribute logic)
} else if (shouldSetAsProps(el, key, nextValue)) {
// Omitted code (handling other attribute logic)
} else {
// Omitted code (handling other attribute logic)
}
}
In fact, the event update mechanism can be further optimized to avoid multiple calls to removeEventListener
and addEventListener
.
function patchProps(el, key, prevValue, nextValue) {
if (/^on/.test(key)) {
const name = key.slice(2).toLowerCase();
let invoker = el.__vei || (el.__vei = {});
if (nextValue) {
if (!invoker[name]) {
// If there is no invoker, create a fake invoker function
invoker[name] = (e) => {
invoker[name].value(e);
};
}
// Assign the actual event handler to the value property of the invoker function
invoker[name].value = nextValue;
// Bind the invoker function as the event handler
el.addEventListener(name, invoker[name]);
} else if (invoker[name]) {
// If the new event handler does not exist and the previously bound invoker exists, remove the binding
el.removeEventListener(name, invoker[name]);
invoker[name] = null;
}
} else if (key === 'class') {
// Omitted code (handling class attribute logic)
} else if (shouldSetAsProps(el, key, nextValue)) {
// Omitted code (handling other attribute logic)
} else {
// Omitted code (handling other attribute logic)
}
}
Looking at the above code, event binding is divided into two steps. First, read the corresponding invoker from el._vei
. If invoker does not exist, create a fake invoker function and cache it in el._vei
. Assign the actual event handler to the invoker.value
property, and then bind the fake invoker function as the event handler to the element. When the event is triggered, the fake event handler is executed, indirectly invoking the actual event handler invoker.value(e)
.
When updating events, since el._vei
already exists, we only need to modify the value of invoker.value
to the new event handler.
This way, updating events can avoid a call to removeEventListener
, improving performance. However, the current implementation still has issues. The problem is that el._vei
currently caches only one event handler at a time. This means that if an element binds multiple events simultaneously, event override will occur.
const vnode = {
type: 'p',
props: {
// Describe events using onXxx
onClick: () => {
alert('clicked');
},
onContextmenu: () => {
alert('contextmenu');
}
},
children: 'text'
};
// Assume renderer is your renderer object
renderer.render(vnode, document.querySelector('#app'));
When the renderer tries to render the vnode provided in the above code, it first binds the click
event and then binds the contextmenu
event. The contextmenu
event handler bound later will override the click
event handler. To solve the event override problem, we need to redesign the data structure of el._vei
. We should design el._vei
as an object, where the keys are event names and the values are corresponding event handler functions. This way, event override issues will be resolved.
Based on the code snippet you provided, this code is mainly used for handling attribute updates on DOM elements, including the logic for event binding and unbinding. In this code, it uses an el._vei
object to cache event handler functions.
Comments