Skip to content

Vue 自定义指令:实现自定义事件修饰符

一、 核心问题

在 Vue 中,我们无法为内置的事件绑定(如 @click)添加自定义修饰符。例如,我们无法直接实现 @click.loading 来在执行异步事件时自动处理全局 Loading 状态。

回顾:Vue 内置的修饰符(如 .stop, .prevent)用于处理特定的 DOM 事件行为,但该机制不对开发者开放。

二、 解决方案

利用 自定义指令 (Custom Directive) 来模拟实现自定义修饰符的功能。我们可以创建一个 v-click 指令,并为其附加修饰符,如 v-click.loading

三、 实现步骤

1. 创建并注册自定义指令

首先,定义一个指令对象,并实现其生命周期钩子函数。

vue
<button v-click.loading="doSomethingAsync">执行异步操作</button>
javascript
// directives/vClick.js

// 用于存储元素与对应事件处理函数的映射,防止内存泄漏
const elementHandlerMap = new WeakMap();

export const vClick = {
  // 当指令绑定的元素被挂载到 DOM 时调用
  mounted(el, binding) {
    // binding.value: 指令绑定的值,即我们要执行的异步函数 doSomethingAsync
    // binding.modifiers: 一个包含所有修饰符的对象,例如 { loading: true }
    const fn = binding.value;
    const modifiers = binding.modifiers;

    // 定义核心事件处理函数
    const handler = async (event) => {
      // 检查是否存在 .loading 修饰符
      if (modifiers.loading) {
        // 1. 开启全局 Loading
        // globalLoading.value = true; // 假设这是一个全局状态
        console.log('触发 Loading...');

        try {
          // 2. 执行真正的异步函数
          await fn(event);
        } finally {
          // 3. 无论成功或失败,都关闭全局 Loading
          // globalLoading.value = false;
          console.log('关闭 Loading...');
        }
      } else {
        // 如果没有 .loading 修饰符,则直接执行函数
        fn(event);
      }
    };

    // 为元素绑定点击事件
    el.addEventListener('click', handler);

    // 将元素和其对应的 handler 存储到 WeakMap 中,便于后续卸载
    elementHandlerMap.set(el, handler);
  },

  // 当指令绑定的元素从 DOM 中卸载时调用
  unmounted(el) {
    // 从 WeakMap 中获取对应的 handler
    const handler = elementHandlerMap.get(el);
    if (handler) {
      // 移除事件监听,防止内存泄漏
      el.removeEventListener('click', handler);
      // 从 WeakMap 中删除该元素的条目
      elementHandlerMap.delete(el);
    }
  }
};

2. 处理修饰符逻辑

mounted 钩子中:

  • 通过 binding.modifiers 检查是否传入了 loading 修饰符。
  • 如果传入了 loading 修饰符:
    1. 在执行 binding.value (即传入的异步函数) 之前,触发全局 loading 状态为 true
    2. 使用 try...finally 块来执行异步函数。
    3. finally 块中,确保无论异步函数成功还是失败,都将全局 loading 状态置为 false
  • 如果没有传入修饰符,则直接执行函数。

3. 关键点:内存管理

  • 问题:在 mounted 中通过 el.addEventListener 手动绑定的事件,Vue 在组件卸载时不会自动移除,这会导致内存泄漏。
  • 解决方案
    1. 使用 WeakMap:创建一个 WeakMap 来存储 DOM元素(el) 与其 事件处理函数(handler) 之间的映射关系。WeakMap 对键是弱引用,当元素被垃圾回收时,对应的键值对会自动消失。
    2. 实现 unmounted 钩子
      • 在组件卸载时,unmounted 钩子会被调用。
      • 通过 elWeakMap 中找到对应的 handler 函数。
      • 调用 el.removeEventListener 来显式地移除事件监听。
      • WeakMap 中删除该条目。

四、 总结

通过自定义指令,我们可以封装任意复杂的逻辑,并响应式地处理指令的修饰符和绑定值。这是实现类似自定义事件修饰符功能的最佳实践,同时务必注意在 unmounted 钩子中进行事件解绑,以避免内存泄漏。