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

相关推荐
夏幻灵11 分钟前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星12 分钟前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_25 分钟前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝28 分钟前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions37 分钟前
2026年,微前端终于“死“了
前端·状态模式
万岳科技系统开发37 分钟前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
程序员猫哥_44 分钟前
HTML 生成网页工具推荐:从手写代码到 AI 自动生成网页的进化路径
前端·人工智能·html
龙飞051 小时前
Systemd -systemctl - journalctl 速查表:服务管理 + 日志排障
linux·运维·前端·chrome·systemctl·journalctl
我爱加班、、1 小时前
Websocket能携带token过去后端吗
前端·后端·websocket
AAA阿giao1 小时前
从零拆解一个 React + TypeScript 的 TodoList:模块化、数据流与工程实践
前端·react.js·ui·typescript·前端框架