Vue 自定义指令 Directive 的高级使用与最佳实践
前言
Vue.js 是一个非常流行的前端框架,它的核心理念是通过声明式的方式来描述 UI 和数据绑定。除了模板语法和组件系统,Vue 还提供了一个强大的功能——自定义指令。
自定义指令可以让我们对 DOM 元素进行底层操作,下面让我们通过一个有趣的例子来看看如何使用自定义指令。
什么是自定义指令?
在 Vue.js 中,指令(Directive)是带有 v- 前缀的特殊属性。当表达式的值改变时,指令会对 DOM 进行相应的更新。Vue 内置了一些常用的指令,比如 v-model、v-if、v-for 等等。
自定义指令让我们可以定义自己的逻辑,对 DOM 元素进行任意的操作,比如添加事件监听器、操作样式、进行动画等等。
自定义步骤
如何创建自定义指令?
创建自定义指令非常简单。我们只需要在 Vue 实例上调用 directive 方法,并传入指令的名称和钩子函数。下面是一个简单的例子:
Vue.directive('focus', {// 当绑定元素插入到 DOM 中时inserted: function (el) {el.focus();}
});
在上面的例子中,我们创建了一个名为 v-focus 的自定义指令,这个指令会在绑定的元素插入到 DOM 中时自动获取焦点。
自定义指令的钩子函数
自定义指令可以定义多个钩子函数,这些钩子函数会在指令生命周期的不同阶段被调用:
- bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted: 当绑定元素插入到 DOM 中时调用。
- update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生改变,也可能没有。
- componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind: 只调用一次,指令与元素解绑时调用。
让我们来实现一个更复杂的自定义指令:点击改变背景色。
Vue.directive('color-swap', {bind: function (el, binding) {el.style.backgroundColor = binding.value;el.addEventListener('click', function () {el.style.backgroundColor = el.style.backgroundColor === 'red' ? 'blue' : 'red';});},unbind: function (el) {el.removeEventListener('click');}
});
在上面的例子中,我们创建了一个名为 v-color-swap 的自定义指令,这个指令会在元素被点击时,切换背景色。
使用自定义指令
使用自定义指令非常简单,只需要在模板中使用 v- 前缀加上指令名即可:
<div v-focus></div>
<button v-color-swap="'red'">Click me to change color</button>
在这个模板中,我们使用了 v-focus 指令让 div 元素自动获取焦点,并使用了 v-color-swap 指令让按钮在点击时切换背景色。
高级用法
1. 自定义拖拽指令
为了更好地理解自定义指令,我们来构建一个更复杂的示例。这次我们将创建一个自定义指令,用于实现拖拽元素。
我们希望通过自定义指令,使得任何绑定了该指令的元素都能够被拖拽。下面是完整的实现代码:
Vue.directive('drag', {bind: function (el) {el.style.position = 'absolute';el.style.cursor = 'move';el.onmousedown = function (event) {// 获取鼠标按下时的坐标和元素初始位置let startX = event.clientX;let startY = event.clientY;let initialLeft = parseInt(el.style.left || 0);let initialTop = parseInt(el.style.top || 0);document.onmousemove = function (event) {// 计算移动距离let dx = event.clientX - startX;let dy = event.clientY - startY;// 更新元素位置el.style.left = initialLeft + dx + 'px';el.style.top = initialTop + dy + 'px';};document.onmouseup = function () {// 移除事件监听器document.onmousemove = null;document.onmouseup = null;};// 防止默认行为导致的选择文字等event.preventDefault();};},unbind: function (el) {el.onmousedown = null;}
});
有了这个自定义指令,我们可以在模板中轻松地使元素具备拖拽功能:
<div v-drag style="width: 100px; height: 100px; background-color: lightcoral;">Drag me!
</div>
现在,当你运行这个页面时,这个 div 元素就可以被拖拽了。
2. 自定义指令的参数
自定义指令还可以接受参数和修饰符。让我们扩展一下拖拽指令,使得它可以接受一个参数,用于限制拖拽的方向。
Vue.directive('drag', {bind: function (el, binding) {el.style.position = 'absolute';el.style.cursor = 'move';el.onmousedown = function (event) {let startX = event.clientX;let startY = event.clientY;let initialLeft = parseInt(el.style.left || 0);let initialTop = parseInt(el.style.top || 0);document.onmousemove = function (event) {let dx = event.clientX - startX;let dy = event.clientY - startY;if (binding.arg === 'x') {el.style.left = initialLeft + dx + 'px';} else if (binding.arg === 'y') {el.style.top = initialTop + dy + 'px';} else {el.style.left = initialLeft + dx + 'px';el.style.top = initialTop + dy + 'px';}};document.onmouseup = function () {document.onmousemove = null;document.onmouseup = null;};event.preventDefault();};},unbind: function (el) {el.onmousedown = null;}
});
在这个扩展的例子中,我们添加了对 binding.arg 的检查,以决定拖拽的方向。如果参数是 x,则只允许水平拖拽;如果参数是 y,则只允许垂直拖拽;否则,允许任意方向的拖拽。
使用带参数的拖拽指令
<div v-drag:x style="width: 100px; height: 100px; background-color: lightcoral;">Drag me horizontally!
</div>
<div v-drag:y style="width: 100px; height: 100px; background-color: lightblue;">Drag me vertically!
</div>
现在,我们有两个 div,一个只能水平拖动,另一个只能垂直拖动。
3. 双向绑定与修饰符
在实际开发中,我们经常需要处理更复杂的场景。接下来,我们将介绍如何利用 Vue 自定义指令实现一个双向绑定的输入框,并且通过修饰符来控制输入的格式(比如只能输入数字)。
我们先创建一个基本的双向绑定输入框指令,这个指令将输入框的值与 Vue 实例中的数据进行绑定:
Vue.directive('model', {bind: function (el, binding, vnode) {el.value = binding.value;el.addEventListener('input', function (event) {vnode.context[binding.expression] = event.target.value;});},update: function (el, binding) {el.value = binding.value;}
});
在这个例子中,我们使用 v-model 这个指令来实现双向绑定。当绑定的值变化时,我们通过 update 钩子函数来更新输入框的值;当输入框的值变化时,我们通过 input 事件来更新 Vue 实例中的数据。
使用双向绑定输入框指令
<div id="app"><input v-model="message" /><p>{{ message }}</p>
</div><script>new Vue({el: '#app',data: {message: ''}});
</script>
通过这个模板,我们可以实现一个简单的双向绑定输入框。当用户在输入框中输入内容时,下方的 p 元素会实时显示当前的输入内容。
使用修饰符控制输入格式
我们可以进一步扩展这个自定义指令,使用修饰符来控制输入的格式。比如,我们只允许用户输入数字:
Vue.directive('model', {bind: function (el, binding, vnode) {el.value = binding.value;el.addEventListener('input', function (event) {let value = event.target.value;if (binding.modifiers.number) {value = value.replace(/[^\d]/g, '');}vnode.context[binding.expression] = value;});},update: function (el, binding) {el.value = binding.value;}
});
在这个扩展的例子中,我们检查 binding.modifiers.number 是否存在。如果存在,我们通过正则表达式只保留数字字符。
使用带修饰符的双向绑定输入框指令
<div id="app"><input v-model.number="message" /><p>{{ message }}</p>
</div><script>new Vue({el: '#app',data: {message: ''}});
</script>
现在,当用户在输入框中输入非数字字符时,这些字符会被自动过滤掉,确保 message 始终是一个数字字符串。
最佳实践
1. 命名规范
为了避免与内置指令冲突,自定义指令的名称应当尽量具有唯一性和描述性。例如,我们可以在自定义指令前添加项目特定的前缀:
Vue.directive('my-focus', {// ...
});
2. 为指令添加清理逻辑
如果自定义指令添加了事件监听器或其他副作用,一定要在 unbind 钩子函数中清理这些副作用,以避免内存泄漏:
Vue.directive('my-event', {bind: function (el, binding) {el.addEventListener('click', binding.value);},unbind: function (el, binding) {el.removeEventListener('click', binding.value);}
});
3. 使用对象字面量传递多个参数
有时我们可能需要为指令传递多个参数,这时可以使用对象字面量的方式:
Vue.directive('highlight', {bind: function (el, binding) {const { color, backgroundColor } = binding.value;el.style.color = color;el.style.backgroundColor = backgroundColor;}
});
在模板中使用:
<p v-highlight="{ color: 'white', backgroundColor: 'blue' }">Highlighted Text</p>
4. 使用指令钩子函数
Vue 自定义指令提供了多个钩子函数,我们可以利用这些钩子函数在指令的不同生命周期阶段执行特定的逻辑:
- bind: 只调用一次,指令第一次绑定到元素时调用。可以在这里进行一次性的初始化设置。
- inserted: 当绑定元素插入到 DOM 中时调用。
- update: 所在组件的 VNode 更新时调用,但不保证其子 VNode 也更新。
- componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind: 只调用一次,指令与元素解绑时调用。可以在这里进行清理操作。
5. 尽量保持指令的职责单一
每个自定义指令应该尽量做一件事,并且做得好。职责单一的指令更容易维护和调试。例如,一个指令负责拖拽,一个指令负责改变颜色,而不是把两者混合在一起。
// 拖拽指令
Vue.directive('drag', {// ...
});// 改变颜色指令
Vue.directive('change-color', {// ...
});
6. 避免在指令中直接操作数据
尽量避免在自定义指令中直接操作 Vue 实例的数据,保持指令的通用性。指令的主要职责是操作 DOM,数据逻辑应该放在组件中处理。
7. 使用 vnode.context 访问组件实例
在自定义指令中,vnode.context 提供了对 Vue 组件实例的访问。这在某些情况下非常有用,例如需要调用组件的方法。
Vue.directive('example', {bind: function (el, binding, vnode) {// 访问组件实例const vm = vnode.context;vm.someMethod();}
});
8. 使用可选参数和修饰符
利用可选参数和修饰符,可以让自定义指令更加灵活和强大。例如,下面的 v-resize 指令可以接受一个参数来指定方向(水平或垂直):
Vue.directive('resize', {bind: function (el, binding) {const direction = binding.arg || 'both';el.style.resize = direction;}
});
使用时:
<textarea v-resize:horizontal></textarea>
9. 提供默认值
在指令的实现中,可以为传递的参数提供默认值,以提高指令的鲁棒性:
Vue.directive('tooltip', {bind: function (el, binding) {const options = binding.value || {};const position = options.position || 'top';const color = options.color || 'black';// 创建并插入 tooltip 元素...}
});
使用时:
<p v-tooltip="{ position: 'bottom', color: 'blue' }">Hover me</p>
10. 使用传递对象简化参数
通过传递对象,可以更方便地传递多个参数,并且让代码更具可读性:
Vue.directive('highlight', {bind: function (el, binding) {const { color, backgroundColor, fontSize } = binding.value;el.style.color = color || 'black';el.style.backgroundColor = backgroundColor || 'white';el.style.fontSize = fontSize || '16px';}
});
使用时:
<p v-highlight="{ color: 'red', backgroundColor: 'yellow', fontSize: '20px' }">Highlighted Text</p>
11. 考虑性能问题
在实现复杂的自定义指令时,注意性能问题。例如,在 update 钩子函数中避免进行复杂的计算或频繁的 DOM 操作。可以使用防抖或节流技术来优化性能。
Vue.directive('debounce-click', {bind: function (el, binding) {let timeout;el.addEventListener('click', () => {clearTimeout(timeout);timeout = setTimeout(() => {binding.value();}, binding.arg || 300);});},unbind: function (el) {clearTimeout(el.__timeout__);}
});
总结
自定义指令是 Vue.js 提供的一个非常强大的功能,它让我们可以对 DOM 元素进行底层操作,并将这些操作封装成指令以便在模板中重复使用。
自定义指令不仅增强了 Vue 的灵活性,也为我们提供了操作 DOM 的强大工具。掌握了这些知识,能让你在实际项目中更好地实现各种复杂的交互需求。
相关文章:

Vue 自定义指令 Directive 的高级使用与最佳实践
前言 Vue.js 是一个非常流行的前端框架,它的核心理念是通过声明式的方式来描述 UI 和数据绑定。除了模板语法和组件系统,Vue 还提供了一个强大的功能——自定义指令。 自定义指令可以让我们对 DOM 元素进行底层操作,下面让我们通过一个有趣的…...

万字图文实战:从0到1构建 UniApp + Vue3 + TypeScript 移动端跨平台开源脚手架
🚀 作者主页: 有来技术 🔥 开源项目: youlai-mall 🍃 vue3-element-admin 🍃 youlai-boot 🍃 vue-uniapp-template 🌺 仓库主页: Gitee 💫 Github …...

在WebStorm遇到Error: error:0308010C:digital envelope routines::unsupported报错时的解决方案
作者:CSDN-PleaSure乐事 欢迎大家阅读我的博客 希望大家喜欢 使用环境:WebStorm 目录 介绍 解决 分析 方法一:设置环境变量 使用WebStorm 使用其他编译器 方法二:使用nvm切换nodejs版本 方法三:更新依赖版本 介…...

数据库产品中SQL注入防护功能应该包含哪些功能
数据库产品中 SQL 注入防护功能应包含以下几方面: 输入验证与过滤功能: 数据类型和格式验证:检查用户输入的数据是否符合预期的数据类型,比如对于一个应该是整数类型的字段,检查输入是否为整数;对于字符串…...

Ribbon客户端负载均衡策略测试及其改进
文章目录 一、目的概述二、验证步骤1、源码下载2、导入IDE3、运行前修改配置4、策略说明5、修改策略 三、最终结论四、改进措施1. 思路分析2. 核心代码3. 测试页面 一、目的概述 为了验证Ribbon客户端负载均衡策略在负载节点失效的情况下,是否具有故障转移的功能&a…...

linux网络编程5——Posix API和网络协议栈,使用TCP实现P2P通信
文章目录 Posix API和网络协议栈,使用TCP实现P2P通信1. socket()2. bind()3. listen()4. connect()5. accept()6. read()/write(), recv()/send()7. 内核tcp数据传输7.1 TCP流量控制7.2 TCP拥塞控制——慢启动/拥塞避免/快速恢复/快速重传 8. shutdown()9. close()9…...

低代码平台中的功能驱动开发:模块化与领域设计
在现代软件开发中,尤其是在低代码平台的背景下,清晰地定义功能和模块是成功的关键。功能驱动开发强调功能的优先性,模块化设计则确保系统的可维护性和可扩展性。本文将探讨如何在低代码平台中有效地将功能与模块结合起来,形成一个…...

HTTP和HTTPS基本概念,主要区别,应用场景
HTTP和 HTTPS是用于在网络中传输数据的协议,虽然它们的功能类似,但在安全性上存在显著差异。 1. HTTP 的基本概念 定义:HTTP 是一种无状态的、面向请求-响应的协议,用于客户端(如浏览器)和服务器之间传输…...

node.js使用Sequelize ORM操作数据库
一、什么是ORM ORM是在数据库和编程语言之间建立一种映射关系,这样可以让我们有非常简单的代码,来实现各种数据库的操作。 例如:使用mysql去查找表(表名称为Articles) SELECT * FROM Articles;但是我们使用ORM的话&…...

STM32-Modbus协议(一文通)
Modbus协议原理 RT-Thread官网开源modbus RT-Thread官方提供 FreeModbus开源。 野火有移植的例程。 QT经常用 libModbus库。 Modbus是什么? Modbus协议,从字面理解它包括Mod和Bus两部分,首先它是一种bus,即总线协议,和…...

100. 不同方向的投影视图
本节课给大家讲解,通过UI按钮界面交互改变threejs相机的观察视角。 x轴方向观察 // 通过UI按钮改变相机观察角度 document.getElementById(x).addEventListener(click, function () {camera.position.set(500, 0, 0); //x轴方向观察camera.lookAt(0, 0, 0); //重新…...

Appium中的api(三)
目录 Appium中的api(三) 1.输入和清空内容 1--输入内容 2--清空内容 2.获取文本内容 3.获取文本位置 4.获取文本的大小(即获取控件的宽和高) 5.滑动api 6.拖拽api 7.如何获取手机分辨率 8.如何截图 9.模拟按键事件api 10.操作通知栏 案例:App自动化模拟 …...

踩坑:关于使用ceph pg repair引发的业务阻塞
概述 在某次故障回溯中,发现引发集群故障,slow io,pg stuck的罪魁祸首竟是做了一次ceph pg repair $pgid。然而ceph pg repair作为使用频率极高的,用来修复pg不一致的常用手段,平时可能很少注意其使用规范和可能带来的…...

瞬间升级!电子文档华丽变身在线题库,效率翻倍✨
👋嘿小伙伴们,有个超赞的秘籍要告诉你们——土著刷题能将你的电子文档一键变身在线题库!😉 你还没发现这个宝藏功能吗?快来瞧瞧! 🌟是不是常被一堆电子版的学习资料搞得头昏脑涨,学习…...

如何动态改变本地的ip
在当今数字化时代,网络连接已成为我们日常生活和工作中不可或缺的一部分。无论是出于隐私保护、突破地域限制,还是为了测试和优化网络应用,动态改变本地IP地址的需求日益增多。本文将详细介绍如何安全、有效地实现这一目标,旨在帮…...

Spring Boot框架在中小企业设备管理中的创新应用
4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式,是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示: 图4-1系统工作原理…...

Ceph入门到精通-Osd db扩容
ceph-bluestore-tool 是一个在 BlueStore 实例上执行低级管理操作的实用程序。 以下命令可用于 ceph-bluestore-tool 语法 ceph-bluestore-tool COMMAND [ --dev DEVICE … ] [ -i OSD_ID ] [ --path OSD_PATH ] [ --out-dir DIR ] [ --log-file | -l filename ] [ --deep ]c…...

windows msvc2017 x64编译AWS SDK CPP库
在本文中,我们将介绍如何编译AWS SDK C库,以便在您的项目中使用。AWS SDK C库提供了与Amazon Web Services交互的接口,允许您在C应用程序中使用AWS服务。 一、准备工作 在开始编译AWS SDK C库之前,请确保您的系统已经安装了以下…...

铜业机器人剥片 - SNK施努卡
SNK施努卡有色行业电解车间铜业机器人剥片 铜业机器人剥片技术是针对传统人工剥片效率低下、工作环境恶劣及生产质量不稳定的痛点而发展起来的自动化解决方案。 面临人工剥片的诸多挑战,包括低效率、工作环境差、人员流动大以及产品质量控制不精确等问题。 人工剥片…...

非接触式竖向位移、水平位移视频实时在线监测的设备分类及选型
前言 视觉是人工智能正在快速发展的一个分支,简单说来,机器视觉就是用机器代替人眼来做测量和判断。在结构健康自动化监测方面,机器视觉采用光学图像结合智能算法和物联网技术,利用先进的智能靶标识别及亚像素处理等技术ÿ…...

Svelte 5 正式发布:新一代前端框架!
10 月 22 日,Svelte 5 正式发布!该版本带来的更新主要包括: 重写框架:Svelte 5 是从头开始重写的,使得应用更快、更小、更可靠,并且代码更一致和符合习惯。 向后兼容:Svelte 5 几乎完全向后兼容…...

85.【C语言】数据结构之顺序表的中间插入和删除及遍历查找
目录 3.操作顺序表 1.分析中间插入函数 函数的参数 代码示例 图片分析 main.c部分改为 在SeqList.h添加SLInsert函数的声明 运行结果 2.分析中间删除函数 函数的参数 代码示例 图片分析 main.c部分改为 在SeqList.h添加SLErase函数的声明 运行结果 承接84.【C语…...

触觉智能Purple Pi OH鸿蒙开发板成功适配OpenHarmony5.0 Release,开启新征程!
10月22日,触觉智能Purple Pi OH鸿蒙开发板迎来了重大系统版本升级,成功适配OpenHarmony5.0 Release,为嵌入式开发者和科技爱好者们带来了全新的机遇与挑战! 触觉智能 Purple Pi OH 开发板一直以来都以其高品质和超高性价比而著称。…...

分布式解决方案---分布式ID
目录 是什么 特点 全局唯一 高并发 高可用 怎么做 实现方案 是什么 分布式ID是指在分布式系统中生成的唯一标识符。由于分布式系统的特点,多个节点可能会同时生成ID,因此需要确保每个ID在整个系统中是唯一的。 重点就是唯一性!&#x…...

httpd服务
文章目录 1、搭建一个网络yum源2、基于域名访问的虚拟主机3、基于端口来访问域名4、搭建个人网站5、加密访问显示自定义网页内容 1、搭建一个网络yum源 [roottest01 conf.d]# cat repo.conf <virtualhost *:80>documentroot /var/www/html/ServerName 10.104.43.154ali…...

Linux系统安装Redis详细操作步骤(二进制发布包安装方式)
安装方式介绍 在Linux系统中,安装软件的方式主要有四种,这四种安装方式的特点如下: 安装方式特点二进制发布包安装软件已经针对具体平台编译打包发布,只要解压,修改配置即可rpm安装软件已经按照redhat的包管理规范进…...

Jenkins和Gitlab整合构建CI/CD流水线
配置环境 虚拟机建议4G起步 192.168.58.199 192.168.58.200 部署Jenkins 部署Jenkins参考这篇文章:Jenkins安装部署_connecting to pkg.jenkins.io (pkg.jenkins.io)|151.-CSDN博客 安装完毕之后根据下图操作 选择git,添加git仓库克隆url,选…...

14 C语言中的关键字
C语言中的关键字 在C语言中,关键字是一些预定义的单词,它们具有特殊的意义和用途。这些关键字不能用作标识符,比如变量名、函数名等,因为它们被保留用于特定的语言结构和操作。 关键字的分类 C语言的关键字可以分为几个主要类别…...

(11)(2.1.7) FETtec OneWire ESCs(一)
文章目录 前言 1 去哪里买 2 连接 3 组态 前言 !Note 此功能在固件版本 4.1.1 及更高版本上可用。 OneWire 是 FETtec 的 ESC 双向通信协议(ESC bi-directional communication protocol)。 FETtec OneWire ESC 的遥测信息被发送回自动驾驶仪: 电…...

Python 异步编程:使用 `asyncio.to_thread` 和 `asyncio.Queue` 处理任务队列
Python 异步编程:使用 asyncio.to_thread 和 asyncio.Queue 处理任务队列 1. 什么是 asyncio.to_thread?2. 什么是 asyncio.Queue?3. 示例代码:使用 asyncio.to_thread 和 asyncio.Queue 处理任务队列示例代码代码解释运行结果 4.…...