简介
在vue中重用代码的方式有:组件、组合式函数。组件是主要的构建模块,而组合式函数更偏重于有状态的逻辑。
指令系统给我们提供了例如:v-model、v-bind,vue系统允许我们自定义指令,自定义指令也是一种重用代码的方式。自定义指令常用于封装一些普通元素的Dom底层访问逻辑。
定义
一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。
自定义指令的钩子函数如下:
javascript
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) {}
}
使用
下面这两个使用的例子都源自网络,在原有的基础上加入了一些改动
一个小栗子,使用全局自定义指令封装防抖代码段
javascript
export const longpress = {
created(el, binding, vNode) {
// console.log("long press event 1");
if (typeof binding.value !== 'function') {
throw 'callback must be a function'
}
// 定义变量
let pressTimer = null
// 创建计时器( 2秒后执行函数 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
handler()
}, 2000)
}
}
// 取消计时器
let cancel = (e) => {
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// 运行函数
const handler = (e) => {
binding.value(e)
}
// 添加事件监听器
el.addEventListener('mousedown', start)
el.addEventListener('touchstart', start)
// 取消计时器
el.addEventListener('click', cancel)
el.addEventListener('mouseout', cancel)
el.addEventListener('touchend', cancel)
el.addEventListener('touchcancel', cancel)
},
// 当传进来的值更新的时候触发
updated(el, { value }) {
el.$value = value
},
// 指令与元素解绑的时候,移除事件绑定
unmounted(el) {
el.removeEventListener('click', el.handler)
},
}
javascript
import App from './App.vue'
import { preventReClick } from './utils/directivebox';
const app = createApp(App)
// 全局指令的挂载
preventReClick(app);
javascript
<template>
<div class="box">
<!-- 这里必须使用el-button,因为这个防抖的功能是通过控制elementPlus中的disable属性实现的 -->
<el-button class="btn" v-preventReClick @click="buttonEvent">使用全局自定义指令,做防抖的处理</el-button>
</div>
</template>
<script setup>
let clickNum = 0
// 使用全局自定义指令处理防抖
/*
运行后可以看到,在连续多次点击button时,3000ms后才会打印一次这个buttonEvent中的log
因为在全局自定义指令中拦截了click的事件,在3000ms内组件被设置为disabled的状态
*/
function buttonEvent() {
clickNum++
console.log("clickNum = ", clickNum);
}
</script>
<style scoped lang="less">
.box {
display: flex;
flex-direction: column;
.btn {
padding: 20px 40px 20px 40px;
background-color: aquamarine;
margin-bottom: 30px;
}
}
</style>
一个小栗子,使用局部自定义指令封装长按元素两秒的点击事件:
javascript
export const longpress = {
created(el, binding, vNode) {
// console.log("long press event 1");
if (typeof binding.value !== 'function') {
throw 'callback must be a function'
}
// 定义变量
let pressTimer = null
// 创建计时器( 2秒后执行函数 )
let start = (e) => {
if (e.type === 'click' && e.button !== 0) {
return
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
handler()
}, 2000)
}
}
// 取消计时器
let cancel = (e) => {
if (pressTimer !== null) {
clearTimeout(pressTimer)
pressTimer = null
}
}
// 运行函数
const handler = (e) => {
binding.value(e)
}
// 添加事件监听器
el.addEventListener('mousedown', start)
el.addEventListener('touchstart', start)
// 取消计时器
el.addEventListener('click', cancel)
el.addEventListener('mouseout', cancel)
el.addEventListener('touchend', cancel)
el.addEventListener('touchcancel', cancel)
},
// 当传进来的值更新的时候触发
updated(el, { value }) {
el.$value = value
},
// 指令与元素解绑的时候,移除事件绑定
unmounted(el) {
el.removeEventListener('click', el.handler)
},
}
javascript
<template>
<div class="box">
<el-button class="btn" v-long-press="longPressEvent">使用全局自定义指令,实现长按处理事件</el-button>
</div>
</template>
<script setup>
import { longpress } from './utils/derective.js'
// 在setup语法糖中使用v打头驼峰命名的方式声明一个局部的自定义指令
const vLongPress = longpress;
function longPressEvent(){
console.log("button的长按点击事件");
}
</script>
<style scoped lang="less">
.box {
display: flex;
flex-direction: column;
.btn {
padding: 20px 40px 20px 40px;
background-color: aquamarine;
margin-bottom: 30px;
}
}
</style>
小结
注意:只有当所需功能只能通过直接的 DOM 操作来实现时,才应该使用自定义指令。其他情况下应该尽可能地使用 v-bind
这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好。