除了 Vue 内置的一系列指令 (比如 v-model
或 v-show
) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。
Vue3 中的自定义指令
1. 生命周期钩子函数
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。同样在每一个生命周期钩子中都可以接收到自定义参数和自定义修饰符。
在 <script setup>
中,任何以 v
开头的驼峰式命名的变量都可以被用作一个自定义指令。
<!-- App.vue -->
<template>
<div>
<button>切换</button>
<!--
钩子函数里面都可以接受这些值
myParam: 自定义参数;
myModifier: 自定义修饰符
-->
<A v-move:myParam.myModifier="{ background: 'pink' }"></A>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, Directive } from 'vue';
import A from './components/A.vue';
import B from './components/B.vue';
let flag = ref<boolean>(true)
const vMove: Directive = {
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted(...args: Array<any>) {
console.log('mounted')
console.log(args);
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
beforeUnmount() {
console.log('beforeUnmount')
},
unmounted() {
console.log('unmounted')
}
}
</script>
<style scoped lang="less"></style>
- 0:该 div 元素。
- 1:传入的参数等。比如 arg 参数,modifiers 自定义修饰符,dir 目录,传入的 value 值,instance 组件实例。
- 2:当前组件的虚拟 DOM
- 3:上一个虚拟 DOM
<!-- App.vue -->
<template>
<div>
<button>切换</button>
<!--
钩子函数里面都可以接受这些值
myParam: 自定义参数;
myModifier: 自定义修饰符
-->
<A v-move:myParam.myModifier="{ background: 'pink' }"></A>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, Directive, DirectiveBinding } from 'vue';
import A from './components/A.vue';
import B from './components/B.vue';
let flag = ref<boolean>(true)
type Dir = {
background: string;
}
const vMove: Directive = {
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted(el: HTMLElement, dir: DirectiveBinding<Dir>) {
console.log('mounted')
console.log(el);
console.log(dir);
el.style.background = dir.value.background;
},
// 传入的数据发生变化(比如此时的background)时触发 beforeUpdate 和 updated
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
beforeUnmount() {
console.log('beforeUnmount')
},
unmounted() {
console.log('unmounted')
}
}
</script>
<style scoped lang="less"></style>
2. 指令简写
<!-- App.vue -->
<template>
<div class="btns">
<button v-has-show="123">创建</button>
<button>编辑</button>
<button>删除</button>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, DirectiveBinding } from 'vue';
import type { Directive } from 'vue'
const vHasShow: Directive = (el, binding) => {
console.log(el, binding) ;
}
</script>
<style scoped lang="less">
.btns {
button {
margin: 10px;
}
}
</style>
应用场景1:按钮鉴权
根据能否从 localStorage(或者后台返回) 中获取数据,来判断是否显示某个按钮。
<!-- App.vue -->
<template>
<div class="btns">
<button v-has-show="'shop:create'">创建</button>
<button v-has-show="'shop:edit'">编辑</button>
<button v-has-show="'shop:delete'">删除</button>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, DirectiveBinding } from 'vue';
import type { Directive } from 'vue'
localStorage.setItem('userId', 'xiuxiu')
// mock 后台返回的数据
const permissions = [
'xiuxiu:shop:create',
// 'xiuxiu:shop:edit', // 后台没有相应数据,则不显示该对应的按钮
'xiuxiu:shop:delete'
]
const userId = localStorage.getItem('userId') as string
const vHasShow: Directive = (el, binding) => {
if(!permissions.includes(userId + ':' + binding.value)) {
el.style.display = 'none'
}
}
</script>
<style scoped lang="less">
.btns {
button {
margin: 10px;
}
}
</style>
应用场景2:鼠标拖拽
拖拽粉色框移动大盒子。
<!-- App.vue -->
<template>
<div v-move class="box">
<div class="header"></div>
<div>内容</div>
</div>
</template>
<script setup lang="ts">
import { Directive, DirectiveBinding } from 'vue';
const vMove:Directive<any,void> = (el:HTMLElement, binding:DirectiveBinding)=> {
let moveElement:HTMLElement = el.firstElementChild as HTMLElement;
console.log(moveElement);
const mouseDown = (e:MouseEvent) => {
// 记录原始位置
// clientX 鼠标点击位置的X轴坐标
// clientY 鼠标点击位置的Y轴坐标
// offsetLeft 鼠标点击的子元素距离其父元素的左边的距离
// offsetTop 鼠标点击的子元素距离其父元素的顶部的距离
let X = e.clientX - el.offsetLeft;
let Y = e.clientY - el.offsetTop;
const move = (e:MouseEvent) => {
console.log(e);
el.style.left = e.clientX - X + 'px';
el.style.top = e.clientY - Y + 'px';
}
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', () => {
document.removeEventListener('mousemove', move);
})
}
moveElement.addEventListener('mousedown', mouseDown);
}
</script>
<style scoped lang="less">
.box {
position: fixed;
height: 200px;
width: 200px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 1px solid #000;
.header {
height: 50px;
width: 100%;
background: pink;
border-bottom: #000 1px solid;
}
}
</style>
应用场景3:懒加载
let imageList = import.meta.glob('./assets/images/*.*', { eager: true })
let imageList = import.meta.glob('./assets/images/*.*')
// 判断图片是否在可视区
const observer = new IntersectionObserver((e)=> {
console.log(e[0]);
})
// 监听元素
observer.observe(el)
<!-- App.vue -->
<template>
<div>
<div>
<img v-lazy="item" width="400" height="500" v-for="item in arr" alt="">
</div>
</div>
</template>
<script setup lang="ts">
import { Directive, DirectiveBinding } from 'vue';
let imageList:Record<string,{default:string}> = import.meta.glob('./assets/images/*.*', { eager: true })
let arr = Object.values(imageList).map(item=>item.default)
console.log(arr);
let vLazy:Directive<HTMLImageElement,string> = async (el,binding)=> {
const def = await import('./assets/pinia.svg')
el.src = def.default
// 判断图片是否在可视区
const observer = new IntersectionObserver((e)=> {
console.log(e[0],binding.value);
if(e[0].intersectionRatio > 0) {
setTimeout(()=> {
el.src = binding.value
},2000)
observer.unobserve(el)
}
})
// 监听元素
observer.observe(el)
}
</script>
<style scoped lang="less"></style>
-
进入可视区比例 > 0:
-
过 2s ,替换图片
Vue2 中的自定义指令
1. 注册自定义指令
- 全局注册
//在main.js中
Vue.directive('focus', {
// bind 钩子函数被调用的时候
bind: function(el, binding, vnode){
// ...
},
// 当被绑定的元素插入到 DOM 中时……
/**
* @param {HTMLElement} el - 绑定的元素
* @param {Object} binding - 一个对象,包含以下属性:
* @param {Object} binding.value - 绑定的值,即 v-focus="value" 中的 value
* @param {Object} vnode - Vue 编译生成的虚拟节点
*/
inserted: function (el, binding, vnode) {
el.focus()
},
// 当指令所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前
update: function (el, binding, vnode, oldVnode) {
// ...
},
// 当指令所在组件的 VNode 及其子 VNode 全部更新后调用
componentUpdated: function (el, binding, vnode, oldVnode) {
// ...
},
// 指令与元素解绑时调用
unbind: function (el, binding, vnode) {
// ...
}
})
-
局部注册
//在Vue组件的配置项中 directives: { "指令名": { inserted () { // 可以对 el 标签,扩展额外功能 // el:使用指令的那个DOM元素 el.focus() } } }
-
使用指令
注意:在使用指令的时候,一定要先注册,再使用,否则会报错
使用指令语法: v-指令名。如:<input type="text" v-focus/>
注册指令时不用加v-前缀,但使用时一定要加v-前缀。
2. 自定义指令的配置
1.在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值
<div v-color="color">我是内容</div>
2.通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数
directives: {
color: {
inserted (el, binding) {
el.style.color = binding.value
},
update (el, binding) {
el.style.color = binding.value
}
}
}