头图

介绍以及快速上手

vue中,我们经常会使用一些vue指令,例如:v-modelv-textv-if 等,这些都是vue内置的指令,在这些指令之外,我们也可以自定义指令,例如:我们相对文字做一个指令,根据指令绑定的值更改文字颜色。
我们先来实现一个最简单的自定义指令:
src -> directives -> modules -> custom.ts
image.png

import { Directive } from "vue";

const custom: Directive = {
  mounted(el, binding) {
    el.style.color = binding.value;
  }
};

export default custom;

将该指令导入到directives文件夹的根目录 index.ts 文件下
image.png

import { App } from "vue";
import custom from "@/directives/modules/custom";

const directives = {
  install(app: App<Element>) {
    // 将一系列自定义指令对象安装到 Vue 应用实例中
    app.directive("custom", custom);
  }
};

// 导出安装函数
export default directives;

然后在main文件引入安装函数
image.png

import { createApp } from "vue";
import App from "@/App.vue";

// 引入自定义指令
import directives from "@/directives/index";

const app = createApp(App);
app.use(directives); // 安装自定义指令
app.mount("#app");

到这里自定义指令就可以使用了,页面使用:

<template>
  <div class="dc-page">
    <div v-custom="'red'">文字变色</div>
  </div>
</template>

image.png
解释
通过上面的例子,我们可以实现一个简单的自定义指令,步骤如下:
1、我们将自定义指令抽离出来,单独放到 directives 文件夹,并且在 directives 文件夹下,将每个指令都细分开来放到 modules 文件夹下。一个项目当中,很大可能有多个自定义指令存在,例如:按钮权限指令、角色权限指令、防抖指令等等,将这些模块独立出来方便管理。
2、在 directives 目录下的 index 定义了一个对象,内部有一个安装函数,用于管理所有的自定义指令,并将自定义指令集成到一个 install 安装函数中,然后导出该对象。
3、最后在 main 文件下引入安装函数,最后通过app.use安装自定义指令。

接下来分别解释一下 main 文件的 app.use 以及 directives 文件(directives/index)的安装函数。
1、app.use
app.use在官网上的解释为:安装一个插件
app.use接收两个参数,第一个参数应是插件本身,可选的第二个参数是要传递给插件的选项。插件可以是一个带 install() 方法的对象,亦或直接是一个将被用作 install() 方法的函数。插件选项 (app.use() 的第二个参数) 将会传递给插件的 install() 方法。
app.use() 对同一个插件多次调用,该插件只会被安装一次。
有了这个解释后,我们就知道刚才在directives文件的安装函数到底是在干什么了。

2、directives 文件(directives/index)的安装函数
在该文件下有一个 directives 对象,该对象是一个安装函数,在上面也说了,插件可以是一个带 install() 方法的对象,所以我们可以将directives对象看成是一个插件。
vue官网中,插件的解释:插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。
一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。
安装函数 install() 有两个参数:app, options,我们在 directives 中就是利用第一个app参数,来注册自定义指令的,这个app就是应用实例。
注册自定义指令方法:
语法:app.directive(指令名称, 指令),其中,指令参数是一个指令钩子(对象),类似于vue的生命周期。

// 指令名称、指令
// app.directive(指令名称, 指令)
app.directive('focus', {
  /* ... */
})

指令钩子

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode) {}
}

指令的具体细节

自定义指令有自己的生命周期钩子,一般来说,我们会在mounted钩子中编写自定义指令。
每个钩子都有三个参数:el, binding, vnode
● el : 自定义指令绑定的dom元素
● binding:自定义指令绑定的值,例如:v-text="hello",那么binding.value 就是"hello"
● vnode:就是vue节点,你可以在vnode上获取绑定的值,例如:<div v-custom="'red'" :rowData="{ id: 1, name: '三国' }">文字变色</div>,可以使用 vnode.ctx.attrs 获取绑定的属性。

<div v-custom="'red'">文字变色</div>
image.png

自定义指令接收多个参数

binding指令参数

以数组的形式传入多个参数

 <div v-custom="['red', 'cyan']">文字变色</div>
const custom: Directive = {
  mounted(el, binding, vnode) {
    console.log("指令参数", binding.value);
  }
};

image.png
以对象的形式传入多个参数

    <div
      v-custom="{
        textColor: 'red',
        bgcColor: 'cyan'
      }"
    >
      文字变色
    </div>
const custom: Directive = {
  mounted(el, binding, vnode) {
    console.log("指令参数", binding.value);
  }
};

image.png

vnode绑定动态参数

v-bind形式传入参数

   <div v-custom="'red'" :rowData="{ name: '兔子先森', project: 'SnowAdmin' }">文字变色</div>
const custom: Directive = {
  mounted(el, binding, vnode) {
    console.log("指令参数", binding.value);
    console.log("vnode", vnode.props);
  }
};

image.png

自定义指令绑定函数
<template>
  <div class="dc-page">
    <button v-custom="getFun">文字变色</button>
  </div>
</template>
<script setup lang="ts">
const getFun = () => {
  console.log("这是函数");
};
</script>
const custom: Directive = {
  mounted(el, binding, vnode) {
    console.log("指令参数", binding.value);
  }
};

image.png
既绑定参数,又绑定函数,对象、数组的形式都可以

<template>
  <div class="dc-page">
    <button
      v-custom="{
        color: 'red',
        fun: getFun
      }"
    >
      文字变色
    </button>
  </div>
</template>

<script setup lang="ts">
const getFun = () => {
  console.log("这是函数");
};
</script>
const custom: Directive = {
  mounted(el, binding, vnode) {
    console.log("指令参数", binding.value);
  }
};

image.png
绑定函数的注意点
自定义指令中,绑定函数后面一定不要带括号,否则会自动调用函数一次,js中函数的调用就是函数+()这里的表现形式也一样,另外,函数后面带了括号,自定义指令里接收的实际上是调用函数后的返回值

<template>
  <div class="dc-page">
    <button v-custom="getFun()">文字变色</button>
  </div>
</template>

<script setup lang="ts">
const getFun = () => {
  console.log("这是函数");
};
</script>
import { Directive } from "vue";

const custom: Directive = {
  mounted(el, binding, vnode) {
    console.log("指令参数", binding.value);
  }
};

export default custom;

image.png
这里函数没有return,所以指令参数接收的是 undefined

<template>
  <div class="dc-page">
    <button v-custom="getFun()">文字变色</button>
  </div>
</template>

<script setup lang="ts">
const getFun = () => {
  console.log("这是函数");
  return "函数返回值"; // 加上返回值
};
</script>

image.png

调用函数并给函数传参

刚才说了,自定义指令不能调用函数,只能传入函数本身,那么有些事件必须调用函数传入当前row,这种情况怎么处理呢?
这里模拟一个点击事件的监听,在点击事件中调用函数,并传入参数

<template>
  <div class="dc-page">
    <button
      v-custom="{
        goodsId: 1255,
        fun: getFun
      }"
    >
      文字变色
    </button>
  </div>
</template>

<script setup lang="ts">
const getFun = (e: any) => {
  console.log("点击事件", e);
};
</script>
import { Directive } from "vue";

const custom: Directive = {
  mounted(el, binding, vnode) {
    el.__onClick__ = () => {
      console.log("参数", binding.value);
      let { goodsId, fun } = binding.value;
      fun(goodsId);
    };
    // 监听绑定dom的点击事件
    el.addEventListener("click", el.__onClick__);
  }
};

export default custom;

image.png
上面的方法,只适合静态数据的绑定传参,如果数据是动态的,binding是无法实时获取的,因为它在mounted钩子中已经获取到值并绑定结束了。

<template>
  <div class="dc-page">
    <button
      v-custom="{
        goodsId: goodsId,
        fun: getFun
      }"
    >
      文字变色
    </button>
  </div>
</template>

<script setup lang="ts">
const goodsId = ref(100);
setTimeout(() => {
  goodsId.value = 200;
}, 1000);

const getFun = (e: any) => {
  console.log("点击事件", e);
};
</script>

image.png
绑定的值:goodsId依旧是100

绑定的点击事件一定要在页面卸载前解绑
import { Directive } from "vue";

const custom: Directive = {
  mounted(el, binding, vnode) {
    el.__onClick__ = () => {
      binding.value(vnode.props);
    };
    // 给dom添加点击事件,这里给el添加了一个__onClick__事件
    el.addEventListener("click", el.__onClick__);
  },
  
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el) {
    // 解绑dom上的点击事件
    el.removeEventListener("click", el.__onClick__);
  }
};

export default custom;

参考文献:

app.use: https://cn.vuejs.org/api/application.html#app-use

vue-插件:https://cn.vuejs.org/guide/reusability/plugins.html

自定义指令:https://cn.vuejs.org/guide/reusability/custom-directives.html...


兔子先森
365 声望15 粉丝

致力于新技术的推广与优秀技术的普及。