一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数:Vue自定义指令文档链接
和组件类似,自定义指令在模板中使用前必须先注册。
1.使用 directives 选项完成了指令的局部注册
export default {
directives: {
// 在模板中启用 v-focus
focus
}
}
2.将一个自定义指令全局注册到应用层级
const app = createApp({})
// 使 v-focus 在所有组件中都可用
app.directive('focus', {
/* ... */
})
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode, prevVnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode, prevVnode) {}
}
在 Vue.js 的自定义指令中,常用的钩子函数有以下几种:
//bind 钩子函数
bind: function(el, binding, vnode) {
// 指令第一次绑定到元素时被调用
}
//bind 钩子函数只会在指令第一次被绑定到元素时执行一次,适合用于初始化指令的行为,比如为元素绑定事件、添加 class 等操作。其中,el 参数是指令所绑定的元素,binding 参数是一个对象,包含了指令的绑定信息,vnode 参数是 Vue.js 中虚拟 DOM 的节点。
//inserted 钩子函数
inserted: function(el, binding, vnode) {
// 指令所绑定的元素被插入到父节点时被调用
}
//inserted 钩子函数会在指令所绑定的元素被插入到父节点时被调用,适合用于操作 DOM 结构的行为,比如添加元素、设置样式等操作。其中,参数的含义和 bind 钩子函数相同。
//update 钩子函数
update: function(el, binding, vnode, oldVnode) {
// 指令所绑定的元素的值发生改变时被调用,但子节点的变化不会触发该函数
}
//update 钩子函数会在指令所绑定的元素的值发生改变时被调用,但不包括子节点的变化,适合用于修改元素的属性或样式等操作。其中,oldVnode 是钩子函数的一个新参数,在新旧节点差异更新时非常有用。
//componentUpdated 钩子函数
componentUpdated: function(el, binding, vnode) {
// 指令所绑定的元素的值或子节点发生改变时被调用
}
//componentUpdated 钩子函数会在指令所绑定的元素的值或子节点发生改变时被调用,适合用于动态修改元素的属性和样式。其中,参数的含义同 inserted 和 update 钩子函数。
//unbind 钩子函数
unbind: function(el, binding, vnode) {
// 指令与元素解绑时被调用
}
//unbind 钩子函数只会在指令与元素解绑时被调用,适合用于清理元素的操作,比如移除事件监听、移除 DOM 元素等操作。其中,参数的含义同 bind 钩子函数。
//以上是常用的自定义指令钩子函数,可以根据需求选择使用。在钩子函数中可以直接操作 DOM 元素、绑定事件、设置样式等,但需要注意性能问题,尽量避免长时间占用主线程。同时,也要注意使用 vnode 与虚拟 DOM 结构进行交互时会影响组件的性能。
tip:除了 el 外,其他参数都是只读的,不要更改它们。
import { useUserStoreHook } from '@/store/modules/user';
import { Directive, DirectiveBinding } from 'vue';
/**
* 按钮权限
*/
export const hasPerm: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
// 「超级管理员」拥有所有的按钮权限
const { roles, perms } = useUserStoreHook();
if (roles.includes('ROOT')) {
return true;
}
// 「其他角色」按钮权限校验
const { value } = binding;
if (value) {
const requiredPerms = value; // DOM绑定需要的按钮权限标识
//具体功能是将有权限的权限项转化为布尔值,判断是否有必要权限。其中 perms 和 requiredPerms 都是数组,分别存放用户具有的权限和所需权限。这里使用 some 方法遍历 perms 数组,如果其中有一个元素requiredPerms 数组中出现过,则返回 true,表示具有必要权限,否则返回 false,表示缺少必要权限。?. 运算符是可选链运算符,它可以在调用对象属性或方法时防止因为对象不存在导致的异常错误。在这里,perms 和 perms 数组都可能是 undefined 或 null,使用 ?. 防止了这种情况下的错误。
const hasPerm = perms?.some(perm => {
return requiredPerms.includes(perm);
});
//权限判断元素是否需要在页面上显示,如果没有所需权限,则将元素从页面上移除
if (!hasPerm) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(
"need perms! Like v-has-perm=\"['sys:user:add','sys:user:edit']\""
);
}
}
};
import { hasPerm } from './permission';
// 全局注册 directive 方法
export function setupDirective(app: App<Element>) {
// 使 v-hasPerm 在所有组件中都可用
app.directive('hasPerm', hasPerm);
}
import { setupDirective } from '@/directive';
const app = createApp(App);
// 全局注册 自定义指令(directive)
setupDirective(app);
<el-button v-hasPerm="['sys:user:add']">新增</el-button>
<el-button v-hasPerm="['sys:user:delete']">删除</el-button>