进阶 vue3自定义指令 vue中常用自定义指令

news2024/11/25 5:43:47

文章目录

  • vue3自定义指令
    • 1.什么是自定义指令?
    • 2.注册自定义指令
      • 2.1 全局注册
      • 2.2 局部注册
        • `<script setup>`中注册:
        • `<script>`中使用:
    • 3.钩子函数参数详解
    • 4.指令传值
    • 5.总结
  • 常用自定义指令案例
    • v-longpress 长按
    • v-debounce 防抖
    • v-throttle 节流
    • v-drag 拖拽
      • `<script>`写法,对比下面`<script setup>`写法
      • `<script setup>`写法,对比上面`<script >`写法

vue3自定义指令

除了 Vue 内置的一系列指令 (比如 v-model 或 v-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。

我们已经介绍了两种在 Vue 中重用代码的方式:组件和组合式函数。组件是主要的构建模块,而组合式函数则侧重于有状态的逻辑。另一方面,自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。

1.什么是自定义指令?

  • 内置指令:
    在Vue中,诸如v-if、v-for、v-on等等被称之为内置指令,它们都是以v-开头的,我们无需注册即可在全局使用它们,内置指令提供了极大的方便给我们,比如v-for指令可以让我们快速循环出很多dom元素等等

  • 自定义指令:
    虽然Vue已经提供了很多内置指令供我们使用,但是人都是贪婪的,总是不满足于现状。所以官方允许我们自定义指令,自定义指令就比较灵活了,我们可以使用任何名称来命名自定义指令,不过我们自定义指定还是需要以v-开头,比如v-focus、v-resize等等。
    如:项目中防抖、节流、点击复制、长按识别、dom拖拽、input 元素自动聚焦这些都可以用自定义指令去完成,但让也可以封装方法,但我感觉还是写成自定义指令比较方便

最后有封装好的例子代码,不同写法,可对比学习

2.注册自定义指令

在Vue中,如果我们定义了一个组件,我们需要注册它才可以使用。自定义指令也是类似的原理,我们需要先注册自定义指令,然后才可以使用它。

2.1 全局注册

在main.ts文件中

import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.directive("focus", {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
  },
  // 在元素被插入到 DOM 前调用
  beforeMount() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted() {},
  // 绑定元素的父组件更新前调用
  beforeUpdate() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated() {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount() {},
  // 绑定元素的父组件卸载后调用
  unmounted() {},
});
app.mount("#app");

上段代码中我们借助Vue3提供的directive方法注册了一个全局的自定义指令,该方法接收两个参数:指令名称、指令钩子函数对象。

钩子函数对象和组件的生命周期一样,这也和Vue2中的自定义指令有着较大的区别。理解这些钩子函数也很简单:我们都知道自定义指令是作用在DOM元素上,那么自定义指令从绑定到DOM元素,再到DOM元素发生变化等等一系列操作,都对应了不同的钩子函数,比如当DOM元素插入到文档中时,自定义指令的mounted等钩子函数就会执行。

调用全局注册的自定义指令,代码如下:

<input type="text" v-focus>

我们可以在任意组件中调用它。

2.2 局部注册

由于Vue3中有<script setup><script>两种写法,两种写法对应的自定义指令的注册写法不太一样。

<script setup>中注册:

script setup写法代码更加简介,我一直推荐vue3用 secipt setup,想明白下面的写法,建议参考:vue3语法糖详解(setup()、<script setup>)

<script setup lang="ts">
// 在模板中启用 v-focus
const vFocus = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {},
  // 在元素被插入到 DOM 前调用
  beforeMount() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted() {},
  // 绑定元素的父组件更新前调用
  beforeUpdate() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated() {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount() {},
  // 绑定元素的父组件卸载后调用
  unmounted() {},
};
</script>

在Vue3中,只要以小写字母v开头的驼峰命名的变量都可以作为一个自定义指令使用,比如上段代码中vFocus就可以在模板中通过v-focus的指令形式使用。

<script>中使用:

export default {
  setup() {
    /*...*/
  },
  directives: {
    // 在模板中启用 v-focus
    focus: {
      // 在绑定元素的 attribute 前
	  // 或事件监听器应用前调用
	  created(el, binding, vnode, prevVnode) {},
	  // 在元素被插入到 DOM 前调用
	  beforeMount() {},
	  // 在绑定元素的父组件
	  // 及他自己的所有子节点都挂载完成后调用
	  mounted() {},
	  // 绑定元素的父组件更新前调用
	  beforeUpdate() {},
	  // 在绑定元素的父组件
	  // 及他自己的所有子节点都更新后调用
	  updated() {},
	  // 绑定元素的父组件卸载前调用
	  beforeUnmount() {},
	  // 绑定元素的父组件卸载后调用
	  unmounted() {},
    }
  }
}

3.钩子函数参数详解

参考官网文档:自定义指令钩子参数
在这里插入图片描述

4.指令传值

我们讲解钩子函数参数时,里面有一个binding参数,这个参数是一个对象,它里面有很多属性,而这些属性中有些就是指令传的值。binding对象中的value就是。看下面代码

<div class="drag" v-drag:jie="'zhang'"></div>
<script lang="ts">
  directives: {
    drag: {
      created(el, binding, vnode, prevVnode) {
        console.log(el, binding);
      },
    },
  },
</script>

结果看图
在这里插入图片描述

5.总结

自定义指令的用处非常多,如果你领略到了它的魅力,那么我相信你一定会爱上它的。Vue3和Vue2自定义指令在注册和使用上有一点,不同,不过原理都是一样的,所以如果你有Vue2的基础,学会Vue3的自定义指令简直就是信手拈来。

常用自定义指令案例

v-longpress 长按

需求:当用户按下鼠标左键或移动端单指触碰,并按住按钮几秒钟时,视为一次长按,触发对应的函数。
思路
定义一个计时器, n 秒后执行函数,n作为参数。
当用户按下按钮时触发 mousedown 或touchstart 事件,启动计时器。
如果 click 、 mouseup 、touchend 或 touchcancel 事件在 n 秒内被触发,则清除计时器,视为普通点击事件。
如果计时器没有在 n秒内清除,则视为一次长按,触发对应的函数。

<template>
  <button v-longPress="changeMsg">修改message</button>
</template>
<script setup lang="ts">
import { DirectiveBinding, ref, VNode } from 'vue';
const changeMsg = () => {
  console.log('长按');
};
let vLongPress = {
  created(el: HTMLElement, binding: DirectiveBinding, vnode, prevVnode) {
    console.log(binding);
    if (typeof binding.value !== 'function') {
      const compName = vNode.context.name;
      let warn = [
        `longpress:`,
      ]`provided expression '${binding.expression}' is not afunction, but has to be `;
      if (compName) {
        warn += `Found in component '${compName}'`;
      }
      console.warn(warn);
    }
    // 定义变量
    let pressTimer = null;
    // 定义函数处理程序
    // 创建计时器( 1秒后执行函数 )
    let start = (e) => {
      if (e.type === 'click' && e.button !== 0) {
        return;
      }
      if (pressTimer === null) {
        pressTimer = setTimeout(() => {
          // 执行函数
          handler();
        }, 2000);
      }
    };
    // 取消计时器
    let cancel = () => {
      // 检查计时器是否有值
      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);
  },
};
</script>

v-debounce 防抖

背景:在开发中,有时遇到要给input或者滚动条添加监听事件,需要做防抖处理。
需求:防止input或scroll事件在短时间内被多次触发,使用防抖函数限制一定时间后触发。
思路
定义一个延迟执行的方法,如果在延迟时间内再调用该方法,则重新计算执行时间。
将事件绑定在传入的方法上。

<template>
  <div>{{ message }}</div>
  <!-- <button v-debounce="changeMsg">修改message</button> -->
  <button v-debounce="{ fn: changeMsg, time: 3000 }">修改message</button>
</template>
<script setup lang="ts">
import { DirectiveBinding, ref, VNode } from 'vue';
let message = ref('123');
const changeMsg = () => {
  message.value = '张三';
  console.log('改变messag');
};
const vDebounce = {
  created(el: HTMLElement, binding: DirectiveBinding, vnode, prevVnode) {
    console.log(binding);

    let timer: null | number = null;
    el.addEventListener('click', () => {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(() => {
        binding.value.fn(); // value = changeMsg
      }, binding.value.time);
    });
  },
  beforeMount() {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {},
  unmounted() {},
};
</script>

或者:这两个做对比,你会发现不一样,运用更灵活,上面说的理解更深刻

<template>
  <button v-debounce="changeMsg">修改message</button>
</template>
<script setup lang="ts">
import { DirectiveBinding, ref, VNode } from 'vue';
let message = ref<string>('123');
const changeMsg = () => {
  message.value = '张三';
  console.log('改变messag');
};
const vDebounce = (el: HTMLElement, binding: DirectiveBinding) => {
  let timer: null | number = null;
  el.addEventListener('click', () => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      binding.value(); // value = changeMsg
    }, 1000);
  });
};
</script>

v-throttle 节流

背景:在开发中,有些提交保存按钮有时候会在短时间内被点击多次,这样就会多次重复请求后端接口,造成数据的混乱,比如立即购买按钮,多次点击就会多次调用创建订单接口。
需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
思路:
定义一个由开关(默认为开)控制是否执行的方法,第一次执行函数时将开关关闭,在规定时间内再调用该方法,则不会再次执行,直至规定时间过后开关打开。
将事件绑定在 click 方法上。

<template>
  <div>{{ message }}</div>
  <button v-throttle="{ fn: changeMsg, time: 3000 }">修改message</button>
</template>
<script setup lang="ts">
import { DirectiveBinding, ref } from 'vue';
let message = ref('123');
1;
const changeMsg = () => {
  console.log('节流');
  message.value = '节流';
};
let vThrottle = {
  created(el: HTMLElement, binding: DirectiveBinding) {
    console.log(binding);
    console.log();

    if (typeof binding.value.fn !== 'function') return;
    el._flag = true; //开关默认为开
    el._timer = null;
    el.handler = function () {
      if (!el._flag) return;
      //执行之后开关关闭
      el._flag && binding.value.fn();
      el._flag = false;
      if (el._timer !== null) {
        clearTimeout(el._timer);
        el._timer = null;
      }
      el._timer = setTimeout(() => {
        el._flag = true; //三秒后开关开启
      }, binding.value.time);
    };
    el.addEventListener('click', el.handler);
  },
  beforeMount() {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {},
  unmounted(el: HTMLElement, binding: DirectiveBinding) {
    el.removeEventListener('click', el.handler);
  },
};
</script>

v-drag 拖拽

页面中某个区域元素需要用户所以拖动,注意需要拖动的元素要给position:absolute等位属性;

<script>写法,对比下面<script setup>写法

<template>
  <div class="drag" v-drag></div>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  directives: {
    drag: {
      created(el, binding) {
        el.onmousedown = function (e) {
          console.log(e);
          //计算出元素距离上边和左边的距离(鼠标点击的位置-元素的位置)
          //这个应该能理解吧
          var disX = e.clientX - el.offsetLeft;
          var disY = e.clientY - el.offsetTop;
          let l;
          let t;
          document.onmousemove = function (e) {
            //鼠标要按住不松开移动才行,松开就会触发onmouseup的事件
            //计算出元素移动后的位置(鼠标位置-元素初始的disX/disY)
            l = e.clientX - disX;
            t = e.clientY - disY;
            el.style.left = l + 'px';
            el.style.top = t + 'px';
          };
          document.onmouseup = function (e) {
            document.onmousemove = null;
            document.onmouseup = null;
            el.style.left = l + 'px';
            el.style.top = t + 'px';
          };
        };
      },
    },
  },
  setup() {},
});
</script>

<style lang="less" scoped>
.drag {
  width: 100px;
  height: 100px;
  background-color: pink;
  position: absolute;
}
</style>

<script setup>写法,对比上面<script >写法

<template>
  <div class="drag" v-drag></div>
</template>

<script lang="ts" setup>
import { ref } from 'vue';

const vDrag = {
  created(el, binding) {
    el.onmousedown = function (e) {
      console.log(e);
      //计算出元素距离上边和左边的距离(鼠标点击的位置-元素的位置)
      //这个应该能理解吧
      var disX = e.clientX - el.offsetLeft;
      var disY = e.clientY - el.offsetTop;
      let l;
      let t;
      document.onmousemove = function (e) {
        //鼠标要按住不松开移动才行,松开就会触发onmouseup的事件
        //计算出元素移动后的位置(鼠标位置-元素初始的disX/disY)
        l = e.clientX - disX;
        t = e.clientY - disY;
        el.style.left = l + 'px';
        el.style.top = t + 'px';
      };
      document.onmouseup = function (e) {
        document.onmousemove = null;
        document.onmouseup = null;
        el.style.left = l + 'px';
        el.style.top = t + 'px';
      };
    };
  },
};
</script>

<style lang="less" scoped>
.drag {
  width: 100px;
  height: 100px;
  background-color: pink;
  position: absolute;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/795850.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kubernetes安装问题记录

kubernetes安装问题记录 【1】未配置 host 文件警告1.1 原因1.2 解决方案 【2】swap 未禁用警告2.1 产生原因2.2 解决方式 【3】containerd 进程禁用了 cri 模块插件3.1 原因3.2 解决方法 问题1-3的完整错误日志【4】因错误中断再次 kubeadm init 报错4.1 原因4.2 解决方案 【5…

【学习篇】SAE J1939协议—常用到的知识点

前言&#xff1a;以下关于SAE J1939协议知识点的学习均抄录自书籍&#xff0c;侵权请联系删除。 故障诊断 SAE J1939诊断应用层定义了用于诊断服务的报文帧&#xff0c;诊断报文&#xff08;DM&#xff09;提供了用于车辆进行诊断和维修的功能。 诊断故障代码定义 SAE J193…

从Vue层面 - 解析发布订阅模式和观察者模式区别

目录 前言一、发布订阅模式什么是发布订阅模式&#xff1f;应用场景 二、观察者模式1&#xff09;什么是观察者模式&#xff1f;2&#xff09;应用场景3&#xff09;vue中的观察者模式观察者&#xff08;订阅者&#xff09; - Watcher目标者&#xff08;发布者&#xff09; - D…

内部类(上)成员内部类,局部内部类的使用

文章目录 前言一、内部类是什么&#xff1f;二、如何使用&#xff1f; 1.成员内部类2.局部内部类总结 前言 如果在一个文件中创建了两个类&#xff0c;那么这两个类是并列关系&#xff0c;不存在哪一个类包含哪一个类的情况。如果在类中再定义一个类&#xff0c;那么这个在类中…

VMPWN的入门系列-1

温馨提示&#xff1a; 今天的文章有点长&#xff0c;图片比较多&#xff0c;请耐心阅读 5.1 实验一 VMPWN1 5.1.1 题目简介 这是一道基础的VM相关题目&#xff0c;VMPWN的入门级别题目。前面提到VMPWN一般都是接收字节码然后对字节码进行解析&#xff0c;但是这道题目不接受字节…

加载已训练好的目标检测YOLOv8,v5,v3,v6模型,对数据集中某张图片中的object打上方框、标出类别,并将图片保存到本地

参考的教程&#xff1a;Python - Ultralytics YOLOv8 Docs 在与ultralytics代码同一层级下新建 predict.py 里面写下面的内容。运行即可 from ultralytics import YOLO from PIL import Image import cv2# 加载计划使用的模型 model YOLO("yolov8n.pt") # load a…

Flask deleteput

Flask delete&put 一、delete 请求1.1 代码1.2 分析1.3 验证 二、put请求2.1 代码2.2 分析2.3 验证 三、总结 Flask get&post 请求传送门&#xff1a;FLASK get&post分析 一、delete 请求 1.1 代码 from flask import Flask, requestapp Flask(__name__)app.ro…

CVPR2023新作:考虑3D一致性的人脸关键点检测

Title: 3D-aware Facial Landmark Detection via Multi-view Consistent Training on Synthetic (三维感知人脸关键点检测&#xff1a;合成数据下多视角一致训练) Affiliation: Texas A&M University Authors: Libing Zeng, Lele Chen, Wentao Bao, Zhong Li, Yi Xu, Jun…

Linux常用命令——dris命令

在线Linux命令查询工具 dris 显示和清空目录堆栈中的内容 补充说明 dris命令用于显示和清空目录堆栈中的内容。 语法 dris(选项)选项 n&#xff1a;显示从左边算起第n笔的目录&#xff1b; -n&#xff1a;显示从右边算起第n笔的目录&#xff1b; -l&#xff1a;显示目录…

2023年第六届河北省研究生数学建模竞赛题目B题Python求解代码

2023年第六届河北省研究生数学建模竞赛题目B题 本文文档与代码视频讲解与下载&#xff1a;【2023河北省研究生数学建模竞赛B题数据集和代码-哔哩哔哩】 https://b23.tv/weulGAO 光伏电池的异常检测与发电产能预测在碳达峰-碳中和的战略背景下&#xff0c;我国的光伏发电技术发…

离谱!学费4万孩子考8分

家长怒怼学费4万孩子考8分&#xff0c;高中单科却只考了8分&#xff0c;而且还有不少孩子考了5分甚至更低&#xff1f; 这试题是有多难啊&#xff1f; 老洪说两句。 现在有一些“贵族学校”或者是一些民办学校&#xff0c;收费项目和标准都由市场来决定&#xff0c;可能比一般学…

openGauss学习笔记-21 openGauss 简单数据管理-GROUP BY子句

文章目录 openGauss学习笔记-21 openGauss 简单数据管理-GROUP BY子句21.1 语法格式21.2 参数说明21.3 示例 openGauss学习笔记-21 openGauss 简单数据管理-GROUP BY子句 GROUP BY语句和SELECT语句一起使用&#xff0c;用来对相同的数据进行分组。您可以对一列或者多列进行分组…

【mysql学习篇】Order by与Group by优化以及排序算法详解

一、Order by与Group by优化 Case1&#xff1a; 分析&#xff1a; 利用最左前缀法则&#xff1a;中间字段不能断&#xff0c;因此查询用到了name索引&#xff0c;从key_len74也能看出&#xff0c;age索引列用在排序过程中&#xff0c;因为Extra字段里没有using filesort 注意…

tinkerCAD案例:12.Minecraft Party Glasses 我的世界派对眼镜

tinkerCAD案例&#xff1a;12.Minecraft Party Glasses 我的世界派对眼镜 原文 In this lesson, you will learn to design a cool pair of party glasses! 在本课中&#xff0c;您将学习设计一副很酷的派对眼镜&#xff01; Start by dragging the Box shape to the Workpla…

【Unity】写的一个小工具用来帮助调试代码

unity的Debug.Log方法当放在Update这样的高频方法中调用时&#xff0c;调试信息就会显得很乱难以观测&#xff0c;因此我自己实现了一个调试辅助工具&#xff0c;可以通过GUI实时显示变量状态在Game视图中&#xff0c;可以在代码的任意处调用即可&#xff08;key不要重复&#…

STM32 USB使用记录:HID类设备(后篇)

文章目录 目的基础说明项目构建与代码调整接收发送代码与测试示例链接报告描述符总结 目的 接上篇&#xff1a; 《STM32 USB使用记录&#xff1a;HID类设备&#xff08;前篇&#xff09;》 USB HID 类的设备有个比较大的好处是大部分时候接入主机中都是可以免驱使用的。这篇文…

通过Vue-cli解决前端跨域问题

1、找到vue.config.js 在vue.config.js当中增加如下配置 devServer: {port: 3001,proxy: {/agent: {target: http://10.8.50.250:6666,ws: false, //true,开启ws, 如果是http代理此处可以不用设置changeOrigin: true, // 如果接口跨域&#xff0c;需要进行这个参…

面向对象编程:深入理解抽象类和关键字

文章目录 1. 关键字1.1 static1.2 final1.3 static final 2. 抽象类2.1 抽象类的推导过程2.2 抽象类能否创建对象&#xff1f;2.3 抽象类的意义2.4 判断 3. 案例&#xff1a;计算圆形和长方形的周长及面积 在Java编程中&#xff0c;我们经常会遇到一些特殊的关键字和概念&#…

Kyuubi入门简介

一、官方简介 HOME — Apache Kyuubi 二、概述 1、一个企业级数据湖探索平台 2、一个高性能的通用JDBC和SQL执行引擎 3、一个基于spark的查询引擎服务 三、优点 1、提供hiveserver2查询spark sql的能力&#xff0c;查询效率更为高效&#xff0c;首次构建连接时会持续保持连…

学习笔记21 list

一、概述 有两种不同的方法来实现List接口。ArrayList类使用基于连续内存分配的实现&#xff0c;而LinkedList实现基于linked allocation。 list接口提供了一些方法&#xff1a; 二、The ArrayList and LinkedList Classes 1.构造方法 这两个类有相似的构造方法&#xff1a…