Vue 的指令是一种在 HTML 模板中以 v- 开头的属性,像 v-showv-ifv-for之类的都属于指令。

除了使用 Vue 内置的指令外,你也可以自定义指令。自定义指令的主要功能就是操作 DOM 元素,当 Vue 的内置指令无法满足你的需求时,就可以考虑使用自定义指令。

在 Vue 的组件方法中,虽然可以直接使用 document.querySelector 获取元素操作,但是这样写的话,后期不利于维护。自定义指令可以把 DOM 操作单独拆分出来,使用的时候只需要给模板标签加 v-指令名 就可以直接操作元素,不需要给元素加 classid 之类的来获取元素。

指令可以在组件中局部定义,也可以在入口文件全局定义。

局部定义

下面定义一个指令在 canvas 中画一个圆:

<template>
  <div>
    <canvas width="200" height="200" v-round></canvas>
  </div>
</template>

<script setup>
const vRound = {
  // 元素加载完成
  mounted: el => {
    // 设置圆的参数
    el.getContext('2d').arc(100, 100, 100, 0, Math.PI * 2, false);
    // 开始绘制
    el.getContext('2d').stroke();
  }
};
</script>

上面的指令在调用它的 canvas 元素被插入到页面后就会执行。

script setup 中以 v 开头的驼峰式命名的变量都可以用作自定义指令,上面的 vRound 在模板中调用的时候可以使用 v-round ,其中的 round 就是指令名称。

如果不使用 script setup 的话,自定义指令需要写在 directives 中,还是上面的在 canvas 中画圆,使用选项式的写法如下:

<template>
  <div>
    <canvas width="200" height="200" v-round></canvas>
  </div>
</template>

<script>
export default {
  directives: {
    round: {
      // 元素被插入到页面
      mounted(el) {
        // 设置圆的参数
        el.getContext('2d').arc(100, 100, 100, 0, Math.PI * 2, false);
        // 开始绘制
        el.getContext('2d').stroke();
      }
    }
  }
}
</script>

directives 中可以直接写指令名称,不需要用 v 开头的驼峰命名。

全局定义

自定义指令也可以在 main.js 入口文件中全局定义,全局定义的指令在每个组件中都能直接调用。

下面在 main.js 中定义一个元素获取焦点的指令:

import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

app.directive('focus', {
  // 元素被插入到页面
  mounted(el) {
    el.focus();
  }
});
app.mount('#app');

下面在组件的 input 上调用这个获取焦点的指令:

<template>
  <div>
    <input type="text" v-focus>
  </div>
</template>

使用插件定义

如果你的项目比较大或是指令比较多的话,直接在 main.js 中定义可能不太方便维护,这种情况下可以考虑使用插件在单独的 JS 文件中定义,main.js 引入指令插件注册后全局都能调用。

下面在一个 directives.js 中通过插件定义指令:

export default {
  install(app) {
    // 定义全局指令
    app.directive('focus', {
      // 元素被插入到页面
      mounted(el) {
        el.focus();
      }
    });
  }
}

上面的指令功能还是在元素加载完成后获取焦点。

下面在 main.js 中引入和注册这个指令插件:

import { createApp } from 'vue';
import App from './App.vue';
import directives from './directives';  // 引入自定义指令的插件

const app = createApp(App);
// 注册指令插件
app.use(directives);
app.mount('#app');

引入注册后在每个组件都能通过 v-focus 调用。

指令钩子函数

自定义指令也包含一些钩子函数,这些函数会在不同阶段被调用,我上面的 mounted 就是在元素加载完成后调用。

下面是指令包含的钩子函数:

const vTest = {
  // 在指令绑定之前调用
  created() {},
  // 在元素被插入到页面前调用
  beforeMount() {},
  // 在绑定元素的父组件和子元素都加载完成后调用
  mounted() {},
  // 绑定元素的父组件更新前调用
  beforeUpdate() {},
  // 绑定元素的父组件和子元素都更新完成后调用
  updated() {},
  // 绑定元素的父组件销毁前调用
  beforeUnmount() {},
  // 绑定元素的父组件销毁后调用
  unmounted() {}
};

钩子参数

上面的钩子函数可以接收 elbindingvnode prevVnode 四个参数,下面是参数说明:

el

绑定的元素,可以直接操作 DOM。

binding

一个对象,其中又包含:

value: 传递给指令的值,写法如下:

<template>
  <div v-test="1 + 3"></div>
  <div v-test="'hello'"></div>
  <div v-test="[1, '呵呵', true]"></div>
</template>

<script setup>
const vTest = {
  mounted(el, binding) {
    // 在控制台输出传递给指令的值
    console.log(binding.value);
  }
};
</script>

上面 3 个 div 传的值在控制台输出如下:

4
hello
array [1, '呵呵', true]

oldValue : 元素更新之前的值,只在 beforeUpdateupdated 中可用。

arg : 传递给指令的参数,写法如下:

<template>
  <div v-test:hello></div>
</template>

<script setup>
const vTest = {
  mounted(el, binding) {
    // 在控制台输出传递给指令的参数
    console.log(binding.arg);
  }
};
</script>

上面通过 binding.arg 获取的参数是一个 String 的 hello

modifiers : 指令的修饰符,写法如下:

<template>
  <div v-test.a1.a2></div>
</template>

<script setup>
const vTest = {
  mounted(el, binding) {
    // 在控制台输出指令修饰符
    console.log(binding.modifiers);
  }
};
</script>

上面的修饰符就是 a1a2 ,控制台输出为 {"a1": true,"a2": true}

instance : 使用该指令的组件实例。

dir : 指令的定义对象。

vnode

绑定元素的顶层 vnode,这里的 vnode 就是虚拟 DOM。

prevNode

之前绑定元素的 vnode,只在 beforeUpdateupdated 钩子中可用。

自定义指令的参数和值也可以动态传递,如下:

<template>
  <div v-test:[arg]="value"></div>
</template>

<script setup>
const value = 'misterma.com';  // 准备传给指令的值
const arg = 'blog';  // 准备传给指令的参数

const vTest = {
  mounted(el, binding) {
    // 输出指令传入的值
    console.log(binding.value);
    // 输出指令传入的参数
    console.log(binding.arg);
  }
};
</script>

简化写法

如果你只需要用到指令的 mountedupdated 钩子的话,定义指令的时候可以直接传入一个函数,如下:

<template>
  <div v-test>111</div>
</template>

<script setup>
const vTest = (el) => {
  el.style.color = 'red';
};
</script>

上面的函数会在 mountedupdated 被调用。