Vue 自定义指令有全局注册和局部注册两种方式。

  1. 通过 Vue.directive( id, [definition] ) 方式注册全局指令。然后在入口文件中进行 Vue.use() 调用。
  • 通过Vue.directive(id,definition)方法可以注册一个全局自定义指令,该方法可以接收两个参数:指令ID和定义对象。
    • 指令ID是指令的唯一标识,定义对象是定义的指令的钩子函数

注册指令

  • 批量注册指令,新建 directives/index.js 文件
import copy from './copy'
import longpress from './longpress'
// 自定义指令
const directives = {
  copy,
  longpress,
}

export default {
  install(Vue) {
    Object.keys(directives).forEach((key) => {
      Vue.directive(key, directives[key])
    })
  },
}
  • main.js 引入并调用
import Vue from 'vue'
import Directives from './JS/directives'
Vue.use(Directives)

指令定义函数提供了几个钩子函数(可选):

指令定义对象的钩子函数

  • bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。
  • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
  • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值。
  • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
  • unbind: 只调用一次, 指令与元素解绑时调用。

binding参数对象包含的属性:

  • name 指令名,不包括v-前缀
  • value 指令的绑定值,例如:v-my-directive="1+1",value的值是2
  • oldValue 指令绑定的前一个值,仅在update和componentUpdated钩子函数中可用,无论值是否改变都可用
  • expression 绑定值的表达式或变量名,例如:v-my-directive="1+1",expression的值是"1+1"
  • arg 传给指令的参数,例如:v-my-directive:foo,arg的值是“foo”
  • modifiers 一个包含修饰符的的对象,例如:v-my-directive:foo.bar,修饰符对象modifiers的值是{foo:true,bar:true}
<div id="element">
    <div v-demo:hello.a.b="message"></div>
 </div>
 
  <script type="text/javascript">
 
    //vnode,Vue编译生成的虚拟节点,oldVnode,Vue编译生成的上一个虚拟节点,仅在update和componentUpdated钩子函数中可用
    Vue.directive('demo',{
        bind:function(el,binding,vnode){
            el.innerHTML=
            'name: '+binding.name+'<br>'+
            'value: '+binding.value+'<br>'+
            'expression: '+binding.expression+'<br>'+
            'argument: '+binding.arg+'<br>'+
            'modifiers: '+JSON.stringify(binding.modifiers)+'<br>'+
            'vnode keys: '+Object.keys(vnode).join(',')
        }
    })
 
     var demo = new Vue({
         el: '#element',
         data:{
            message:'你好世界!!!'
         }
     })
 
 </script>

自定义指令的绑定值

  • 自定义指令的绑定值除了可以是data中的属性之外,还可以是任意合法的JavaScript表达式,例如数值常量、字符串常量、对象字面量等。
<div id="element">
    <p v-ikun=" {size:38 , color:'green'} ">aaavvvvvddddddddd</p>
 </div>
  
 <script type="text/javascript">
 
    Vue.directive('ikun',{
        bind:function(el,binding){
            el.style.fontSize=binding.value.size+'px';
            el.style.color=binding.value.color;
        }
    })
 
     var demo = new Vue({
         el: '#element'
     })
 
 </script>

示例

  • v-debounce

    • 背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如新增表单的提交按钮,多次点击就会新增多条重复的数据。
  • 需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。

思路:

  1. 定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。
  2. 将时间绑定在 click 方法上。
const debounce = {
  inserted: function (el, binding) {
    let timer
    el.addEventListener('keyup', () => {
      if (timer) {
        clearTimeout(timer)
      }
      timer = setTimeout(() => {
        binding.value()
      }, 1000)
    })
  },
}

export default debounce
  • 使用:给 Dom 加上 v-debounce 及回调函数即可
<template>
  <button v-debounce="debounceClick">防抖</button>
</template>

<script> 
export default {
  methods: {
    debounceClick () {
      console.log('只触发一次')
    }
  }
} 
</script>
  • v-permission

  • 背景:在一些后台管理系统,我们可能需要根据用户角色进行一些操作权限的判断,很多时候我们都是粗暴地给一个元素添加 v-if / v-show 来进行显示隐藏

  • 但如果判断条件繁琐且多个地方需要判断,这种方式的代码不仅不优雅而且冗余。针对这种情况,我们可以通过全局自定义指令来处理。

  • 需求:自定义一个权限指令,对需要权限判断的 Dom 进行显示隐藏。

思路:

  1. 自定义一个权限数组
  2. 判断用户的权限是否在这个数组内,如果是则显示,否则则移除 Dom
function checkArray(key) {
  let arr = ['1', '2', '3', '4']
  let index = arr.indexOf(key)
  if (index > -1) {
    return true // 有权限
  } else {
    return false // 无权限
  }
}

const permission = {
  inserted: function (el, binding) {
    let permission = binding.value // 获取到 v-permission的值
    if (permission) {
      let hasPermission = checkArray(permission)
      if (!hasPermission) {
        // 没有权限 移除Dom元素
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  },
}

export default permission
  • 使用:给 v-permission 赋值判断即可
<div class="btns">
  <!-- 显示 -->
  <button v-permission="'1'">权限按钮1</button>
  <!-- 不显示 -->
  <button v-permission="'10'">权限按钮2</button>
</div>
  • vue-waterMarker 需求:给整个页面添加背景水印

思路:

  1. 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
  2. 将其设置为背景图片,从而实现页面或组件水印效果
function addWaterMarker(str, parentNode, font, textColor) {
  // 水印文字,父元素,字体,文字颜色
  var can = document.createElement('canvas')
  parentNode.appendChild(can)
  can.width = 200
  can.height = 150
  can.style.display = 'none'
  var cans = can.getContext('2d')
  cans.rotate((-20 * Math.PI) / 180)
  cans.font = font || '16px Microsoft JhengHei'
  cans.fillStyle = textColor || 'rgba(180, 180, 180, 0.3)'
  cans.textAlign = 'left'
  cans.textBaseline = 'Middle'
  cans.fillText(str, can.width / 10, can.height / 2)
  parentNode.style.backgroundImage = 'url(' + can.toDataURL('image/png') + ')'
}

const waterMarker = {
  bind: function (el, binding) {
    addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor)
  },
}

export default waterMarker


// - 使用,设置水印文案,颜色,字体大小即可

<template>
  <div v-waterMarker="{text:'11111版权所有',textColor:'rgba(180, 180, 180, 0.4)'}"></div>
</template>
 

vue3

自定义指令跟组件一样,也是有生命周期的,我们的操作都是定义在对应的生命周期中,然后进行操作的 七个钩子函数 在 Vue3 中,自定义指令的钩子函数主要有如下七种(这块跟 Vue2 差异较大):

自定义指令生命周期

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

  • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。

  • mounted:在绑定元素的父组件被挂载后调用,大部分自定义指令都写在这里。

  • beforeUpdate:在更新包含组件的 VNode 之前调用。

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

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

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

虽然钩子函数比较多,看着有点唬人,不过我们日常开发中用的最多的其实是 mounted 函数。

生命周期四个参数

这里七个钩子函数,钩子函数中有回调参数,回调参数有四个,含义基本上和 Vue2 一致:

  • el:指令所绑定的元素,可以用来直接操作 DOM,我们松哥说想实现一个可以自动判断组件显示还是隐藏的指令,那么就可以通过 el 对象来操作 DOM 节点,进而实现组件的隐藏。

  • binding:我们通过自定义指令传递的各种参数,主要存在于这个对象中,该对象属性较多,如下属性是我们日常开发使用较多的几个

    • name:指令名,不包括 v- 前缀。

    • value:指令的绑定值,例如:v-hasPermission="['user:delete']" 中,绑定值为 'user:delete', 注意 这个绑定值可以是数组也可以是普通对象,关键是看你具体绑定的是什么

    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。

    • arg:传给指令的参数,可选。例如 v-hasPermission:[name]="'zhangsan'" 中,参数为 "name"。

  • vnode:Vue 编译生成的虚拟节点。

  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

const usersPermissions = ['user'];
 
app.directive('hasPermission', {
    mounted(el, binding, vnode) {
        const {value} = binding;
        let f = usersPermissions.some(p => {
            return p.indexOf(value) !== -1;
        });
        if (!f) {
            el.parentNode && el.parentNode.removeChild(el);
        }
    }
})
  1. 先从 binding 中提取出 value 的值,这就是当前控件所需要的权限
  2. 然后遍历 usersPermissions 用一个 some 函数,去查看 usersPermissions 中是否有满足条件的值
  3. 如果没有,说明当前用户不具备展示该组件所需要的权限,那么就要隐藏这个组件,隐藏的方式就是获取到当前组件的父组件,然后从父组件中移除当前组件即可。

这是一个全局的指令,定义好之后,我们就可以在组件中直接使用了:

<button @click="btnClick" v-hasPermission="['user:delete']">删除用户</button>

自定义指令

  • 私有自定义指令
    • 在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。
<script>
export default {
  directives: {
      // 自定义私有指令focus,在使用的时候要用 v-focus 。
    focus: {
      mounted(el, binding, vnode) => {
        //el是对应的dom,可以通过el给对应添加事件监听 el.addEventListener
        // binding.value 就是通过 = 绑定的值,在传值的时候传到这 binding.value
        //vnode是节点
        }
    },
  },
}
</script>

// 在使用自定义指令时,需要加上 v- 前缀。示例代码如下:

<!-- 声明自定义私有指令focus,在使用的时候要用 v-focus 。 -->    
<input v-focus/>

全局自定义指令

  • 定义指令
    • 全局共享的自定义指令需要通过“单页面应用程序的实例对象”进行声明,示例代码如下:
import { createApp } from 'vue'
 
const app = createApp({
  /* ... */
})
 
// 注册(对象形式的指令)
app.directive('my-directive', {
  /* 自定义指令钩子 */
    mounted(el, bindings) {
      
    }
 
})
 
// 注册(函数形式的指令)
app.directive('my-directive', (el, binding, vnode) => {
        //el是对应的dom,可以通过el给对应添加事件监听 el.addEventListener
        // binding.value 就是通过 = 绑定的值,在传值的时候传到这 binding.value
        //vnode是节点
        })

demo vue3

<template>
    <div>
        <button v-onceClick="10000" @click="btnClick">ClickMe</button>
    </div>
</template>
 
<script>
 
    import {ref} from 'vue';
 
    export default {
        name: "MyVue01",
        setup() {
            const a = ref(1);
            const btnClick = () => {
                a.value++;
            }
            return {a, btnClick}
        },
        directives: {
            onceClick: {
                mounted(el, binding, vnode) {
                    el.addEventListener('click', () => {
                        if (!el.disabled) {
                            el.disabled = true;
                            setTimeout(() => {
                                el.disabled = false;
                            }, binding.value || 1000);
                        }
                    });
                }
            }
        }
    }
</script>