Vue3 自定义指令Custom Directives

简介

在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 这样的内置指令来声明式地使用模板,这样更高效,也对服务端渲染更友好。

相关推荐
uhakadotcom6 分钟前
Caddy Web服务器初体验:简洁高效的现代选择
前端·面试·github
前端菜鸟来报道8 分钟前
前端react 实现分段进度条
前端·javascript·react.js·进度条
花楸树32 分钟前
前端搭建 MCP Client(Web版)+ Server + Agent 实践
前端·人工智能
wuaro32 分钟前
RBAC权限控制具体实现
前端·javascript·vue
专业抄代码选手36 分钟前
【JS】instanceof 和 typeof 的使用
前端·javascript·面试
用户00798136209737 分钟前
6000 字+6 个案例:写给普通人的 MCP 入门指南
前端
用户876128290737441 分钟前
前端ai对话框架semi-design-vue
前端·人工智能
干就完了144 分钟前
项目中遇到浏览器跨域前端和后端解决方案以及大概过程
前端
我是福福大王1 小时前
前后端SM2加密交互问题解析与解决方案
前端·后端
实习生小黄1 小时前
echarts 实现环形渐变
前端·echarts