如何在 Vue3 中自定义指令 | 全面讲解

350 阅读6分钟

一、什么是自定义指令

在 Vue 3 中,自定义指令用于实现对普通 DOM 元素的底层操作,满足特定的功能需求。通过自定义指令,可以将一些常见的操作逻辑封装起来,在多个地方复用,增强代码的可读性、可维护性和灵活性。一个自定义指令通常是一个对象,其中可以定义各种钩子函数来执行不同阶段的操作。

在本文中,我将介绍自定义指令的钩子函数、注册方式、参数传递、共享自定义指令、使用场景等多个方面的知识。

二、自定义指令的钩子函数

在 Vue 3 中,一个指令定义对象可以提供以下钩子函数(均为可选):

created: 在绑定元素的属性或事件监听器被应用之前调用。该指令需要附加需要在普通的 v-on 事件监听器前调用的事件监听器时,这很有用。

beforemount: 当指令第一次绑定到元素并且在挂载父组件之前执行。

mounted: 绑定元素的父组件被挂载之后调用。

beforeupdate: 在更新包含组件的 vnode 之前调用。

updated: 在包含组件的 vnode 及其子组件的 vnode 更新后调用。

beforeunmount: 在卸载绑定元素的父组件之前调用。

unmounted: 当指令与元素解除绑定且父组件已卸载时,只调用一次。

从 Vue 2 升级到 Vue 3,自定义指令的生命周期钩子函数发生了一些变化:

bind 函数被替换成了 beforemount。

update 被移除。

componentUpdated 被替换成了 updated。

unbind 被替换成了 unmounted。

inserted 被移除。

三、自定义指令的注册方式

全局自定义指令

在 Vue 应用的入口文件(如 main.js)或单独的 JavaScript 文件中,使用app.directive()方法进行注册。注册后可在整个应用的任何组件中使用。

import { createApp } from 'vue';

const app = createApp({ 
  // 这里添加你的组件等其他配置 
});

// 注册一个全局自定义指令
app.directive('custom-directive', { 
  bind(el, binding, vnode) { 
    // bind 钩子函数,在指令第一次绑定到元素时调用 
    // 可以执行一次性的初始化操作 
    console.log('directive bound!'); 
  }, 
  inserted(el, binding, vnode) { 
    // inserted 钩子函数,在绑定元素插入父节点时调用 
    console.log('element inserted into parent node!'); 
  }, 
  update(el, binding, vnode, oldVnode) { 
    // update 钩子函数,在绑定元素所在的模板更新时调用 
    // 可以通过比较更新前后的绑定值进行一些操作 
    console.log('directive updated!'); 
  }, 
  componentUpdated(el, binding, vnode, oldVnode) { 
    // componentUpdated 钩子函数,在绑定元素所在模板完成一次更新周期时调用 
    console.log('directive component updated!'); 
  }, 
  unbind(el, binding, vnode) { 
    // unbind 钩子函数,在指令与元素解绑时调用 
    console.log('directive unbound!'); 
  } 
});

在上述代码中,使用app.directive方法注册了一个名为'custom-directive'的全局自定义指令。在指令定义对象中,可以提供bind、inserted、update、componentUpdated和unbind等钩子函数,用于在不同阶段执行相应的操作。

注册全局指令后,在任何组件中都可以使用v-custom-directive来应用该指令,例如:

<template>
  <div v-custom-directive="'red'"> <!-- 这里使用全局自定义指令 --> </div>
</template>

局部自定义指令

局部自定义指令只能在定义它的组件内部使用。在组件的选项中通过 directives 对象来定义局部指令,只有该组件及其子组件可以使用这些指令。

export default {
  directives: {
   'y-directive': {
      beforeMount(el, binding, vnode, prevVnode) {
        // 自定义指令的逻辑
      },
      updated(el, binding, vnode, prevVnode) {
        // 组件更新时的逻辑
      }
    }
  }
}

四、共享多个自定义指令

如果有多个自定义指令需要共享,可以将它们放在一个单独的文件中进行管理,例如创建一个directives.js文件,然后在该文件中统一注册和导出:

// directives.js
import { createApp } from 'vue';

const directives = {
  'draggable': { 
    // 可拖拽指令的具体定义 
  },
  'longpress': { 
    // 长按指令的具体定义 
  }
};

export default {
  install(app) {
    Object.keys(directives).forEach((key) => {
      app.directive(key, directives[key]);
    });
  }
};

在main.js或其他入口文件中引入并使用这个模块:

import { createApp } from 'vue';
import directives from './directives.js';

const app = createApp({...});

app.use(directives);

通过这种方式,可以方便地管理和共享多个自定义指令。

五、自定义指令的参数

binding对象

在 Vue 3 的自定义指令中,可以通过指令的binding对象来获取传递给指令的参数。binding对象包含了一些属性,用于访问指令的值、参数、修饰符等。

app.directive('custom-directive', (el, binding) => {
  // 获取参数值
  const argValue = binding.arg; 
  const value = binding.value; 
  
  // 根据参数进行相应的操作
  if (argValue === 'pecificArg') {
    el.style.backgroundColor = value; 
  }
});

在上述示例中,定义了一个名为custom-directive的自定义指令。在指令的钩子函数中,通过binding.arg获取到传递给指令的参数(在使用指令时指定,例如v-custom-directive:specificArg="colorValue"中的specificArg),通过binding.value获取到指令的值(例如上述例子中的colorValue)。

然后,可以根据获取到的参数值进行相应的操作,例如设置元素的背景颜色。

例如,在模板中这样使用该指令:

<div v-custom-directive:specificArg="red">这是一个带有自定义指令的元素</div>

在这个例子中,custom-directive指令会将该元素的背景颜色设置为红色(red)。其中,specificArg是参数,其值为'specificArg',red是指令的值。

binding对象包含的其他属性:

1.oldValue: 仅在 beforeUpdate 和 updated 钩子函数中可用。它表示指令绑定的值在本次更新之前的值。通过比较 value 和 oldValue,可以实现根据值的变化进行相应的操作。

2.expression: 这是指令表达式的字符串形式。例如,如果您使用 v-custom-directive="count + 1",那么 expression 就是 "count + 1" 。

3.modifiers: 这是一个对象,其中的键是传递给指令的修饰符,值为 true 。例如,如果您使用 v-custom-directive.modifier1.modifier2="value",那么 modifiers 对象就会包含 { modifier1: true, modifier2: true } 。

4.instance: 指向使用该指令的组件实例,可以访问组件的方法或数据。

六、自定义指令的底层实现原理

在底层实现方面,Vue 的指令系统在模板编译阶段,会扫描模板中的节点,解析到指令属性后,将其分配给对应的指令对象进行处理。在生成虚拟 DOM(vnode)和真实 DOM 的 patch 阶段,当指令的值发生变化或组件的 vnode 更新时,会根据指令对象中的钩子函数进行相应的操作,从而实现对 DOM 元素的修改或其他自定义行为。

例如,在 update 钩子函数中,可以根据 binding.value 的变化来决定是否以及如何更新元素的属性、样式等。还有就是除了 el 之外,其他参数通常应该是只读的,不要进行修改,如果需要在钩子函数之间共享数据,建议通过元素的 dataset 来进行。