Vue 自定义指令有全局注册和局部注册两种方式。
- 通过
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
- 背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如新增表单的提交按钮,多次点击就会新增多条重复的数据。
需求:防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次。
思路:
- 定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。
- 将时间绑定在 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 进行显示隐藏。
思路:
- 自定义一个权限数组
- 判断用户的权限是否在这个数组内,如果是则显示,否则则移除 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
需求:给整个页面添加背景水印
思路:
- 使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。
- 将其设置为背景图片,从而实现页面或组件水印效果
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);
}
}
})
- 先从
binding
中提取出value
的值,这就是当前控件所需要的权限 - 然后遍历
usersPermissions
用一个some
函数,去查看usersPermissions
中是否有满足条件的值 - 如果没有,说明当前用户不具备展示该组件所需要的权限,那么就要隐藏这个组件,隐藏的方式就是获取到当前组件的父组件,然后从父组件中移除当前组件即可。
这是一个全局的指令,定义好之后,我们就可以在组件中直接使用了:
<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>