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

相关推荐
Ticnix14 分钟前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人17 分钟前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl21 分钟前
OpenClaw 深度技术解析
前端
崔庆才丨静觅25 分钟前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人33 分钟前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼36 分钟前
shadcn/ui,给你一个真正可控的UI组件库
前端
布列瑟农的星空40 分钟前
前端都能看懂的Rust入门教程(三)——控制流语句
前端·后端·rust
Mr Xu_1 小时前
Vue 3 中计算属性的最佳实践:提升可读性、可维护性与性能
前端·javascript
jerrywus1 小时前
我写了个 Claude Code Skill,再也不用手动切图传 COS 了
前端·agent·claude
玖月晴空1 小时前
探索关于Spec 和Skills 的一些实战运用-Kiro篇
前端·aigc·代码规范