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

Handling Event Rendering in Vue

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.

Cloud Computing and Its Applications Vue Render Mounting and Updating

Comments