当前位置: 首页 > news >正文

Vue.js 中的指令(Vue自定义指令)

Vue.js 中的指令作用

在 Vue.js[1] 中,指令是个非常重要的概念,它可以用来扩展 Vue.js 的功能,提供了丰富的指令来简化开发者的工作。本文将对除了内置指令外,Vue.js 还支持自定义指令,开发者可以根据自己的需求扩展 Vue.js 的指令库。Vue.js 3.x 相较于 Vue.js 2.x 在自定义指令方面进行了一些改进,本文将介绍 Vue.js 3.x 中自定义指令的使用方法。

什么是自定义指令

1. 概念介绍

在 Vue.js 中,指令 (Directives) 是一种带有 v- 前缀的特殊属性。它的作用是「当其绑定的元素被插入到 DOM 中时,会立即执行一些行为」。 Vue.js 中有许多内置指令,比如:

  • v-model:在表单元素上创建「双向数据绑定」

  • v-show:根据表达式之真假值,「切换元素的 display CSS 属性」

  • v-if:根据表达式之真假值「渲染或销毁元素」

  • v-for:基于一个数组来渲染一个列表。

这些指令让我们可以更加声明式地操作 DOM,隐藏复杂的 DOM 操控逻辑。 除了内置的指令,Vue.js 也允许我们注册自定义指令[2]。自定义指令「允许我们在渲染的 DOM 元素上应用自定义的行为」

基础使用

以全局自定义指令为例,通过全局方法 app.directive(name, options) 进行注册,并使用 v- 前缀在模板中应用。directive() 方法接收两个参数:

  • name:指令名称,如 focus

  • options:指令配置对象,其中包含「指令的钩子函数」

下面以自定义指令 v-focus作为示例介绍,首先创建 v-focus指令:

const app = createApp({});
app.directive("focus", {// 当绑定元素插入到 DOM 中时......mounted(el) {// 聚焦元素el.focus();},
});

然后在模板中使用:

<input v-focus />

当输入框挂载到 DOM 时,它将自动获得焦点。 一个自定义指令定义对象可以提供以下「钩子函数」

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) {},
};

每个钩子函数的参数包括:

  • el:指令绑定到的元素。可以用于直接操作 DOM。

  • binding:一个对象,包含valueoldValueargmodifiersinstancedir属性。

  • vnode:代表绑定元素的底层 VNode。

  • prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

参数的详细介绍,可以查看文档《Hook Arguments[3]》。

🗂️ 自定义指令分类

1. 按指令注册方式分类

自定义指令按「指令注册方式」可以分为:「全局指令」「局部指令」

  • 「全局指令」

全局注册的指令可以「在应用程序的任何组件中使用」,通常在 Vue 的 app 实例上通过 directive()进行注册:

const app = createApp({});
app.directive("focus", {// 当绑定元素插入到 DOM 中时......mounted(el) {// 聚焦元素el.focus();},
});
  • 「局部指令」

局部注册的指令仅「在其注册的组件中可用」,通常在组件配置对象中进行注册:

const Component = defineComponent({directives: {focus: {mounted(el) {el.focus();},},},render() {const { directives } = this.$options;return [withDirectives(h("input"), [[directives.focus]])];},
});

2. 按指令实现方式分类

自定义指令按「指令实现方式」可以分为:「对象指令」「函数指令」

  • 「对象指令 ObjectDirective」

对象指令以对象形式实现,提供了更多的选项和生命周期方法:

const app = createApp({});
app.directive("focus", {// 当绑定元素插入到 DOM 中时......mounted(el) {// 聚焦元素el.focus();},
});

在源码里面接口类型定义如下:

export interface ObjectDirective<T = any, V = any> {created?: DirectiveHook<T, null, V>;beforeMount?: DirectiveHook<T, null, V>;mounted?: DirectiveHook<T, null, V>;beforeUpdate?: DirectiveHook<T, VNode<any, T>, V>;updated?: DirectiveHook<T, VNode<any, T>, V>;beforeUnmount?: DirectiveHook<T, null, V>;unmounted?: DirectiveHook<T, null, V>;getSSRProps?: SSRDirectiveHook;
}
  • 「函数指令 FunctionDirective」

函数指令是对象指令的简化形式,使用起来更加简单,适合于只需执行一些操作的场景。 通常仅仅需要在 mounted 和 updated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下可以直接用一个函数来定义指令,如下所示:

app.directive("color", (el, binding) => {// 这会在 `mounted` 和 `updated` 时都调用el.style.color = binding.value;
});

在源码里面接口类型定义如下:

export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V>;export type DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = (el: T,binding: DirectiveBinding<V>,vnode: VNode<any, T>,prevVNode: Prev
) => void;

⚠️ 注意事项

在使用自定义指令时,有一些注意事项需要牢记。这些包括指令命名的规则、指令的生命周期和钩子函数的执行顺序等。 以下是 5 个常见注意事项:

  • 指令需要使用多个参数时,可以传递一个 JS 对象字面量

<div v-demo="{ color: 'white', text: 'hello!' }"></div>;app.directive("demo", (el, binding) => {console.log(binding.value.color); // => "white"console.log(binding.value.text); // => "hello!"
});
  1. 不推荐在组件上使用自定义指令,因为组件可能含有多个根节点

和 attribute 不同,指令不能通过 v-bind="$attrs" 来传递给一个不同的元素。

<MyComponent v-demo="test" />
<!-- MyComponent 的模板 --><div><!-- v-demo 指令会被应用在此处 --><span>My component content</span>
</div>
  1. 自定义指令第二个参数支持一个对象配置

定义指令时,第一个参数除了指令名称外,还接受一个对象,该对象包含指令钩子函数,这与 Vue2 不同,需要注意。

app.directive("focus", {mounted(el) {el.focus();},
});
  1. 在 v-for 渲染的元素上,指令钩子多次调用

<ul><li v-for="item in list" v-focus>
</ul>

focus 指令的钩子函数会以每个 li 元素为参数调用多次。

  1. v-on 修饰符 .native 不再支持

编辑器会提示警告“'.native' modifier on 'v-on' directive is deprecated.

<!-- 会产生警告, .native 修饰符已废除 -->
<input @click.native="doSomething">

在 Vue3 中直接使用 @click 即可监听原生事件。

💡 使用示例

接下来以 3 个使用示例做演示:

v-preview

通过 v-preview 自定义指令,实现「图片预览功能」。 指令实现:

// 指令实现
export default {mounted(el) {el.addEventListener("mouseenter", (e) => {const img = e.target;const src = img.src;const parent = img.closest(".img-preview-container");parent.style.position = "relative";const preview = document.createElement("div");preview.style.position = "absolute";preview.style.top = 0;preview.style.left = 0;preview.style.background = "url(" + src + ") no-repeat center center";preview.style.backgroundSize = "contain";preview.style.width = "100%";preview.style.height = "100%";parent.append(preview);});el.addEventListener("mouseleave", (e) => {const parent = e.target.closest(".img-preview-container");parent.style.position = "";const preview = parent.querySelector("div");preview.remove();});},
};

注册指令:

import { createApp } from "vue";
import vPreview from "./directives/vPreview";
import App from "./App.vue";
const app = createApp(App);// 注册指令
app.directive("preview", vPreview);app.mount("#app");

使用指令:

<div class="img-preview-container"><img v-for="src in imgSrcs" :src="src" v-preview />
</div>

当鼠标移入 img 元素时,会根据其 src 展示对应的图片预览。当鼠标移出时,图片预览会消失。这个 v-preview 自定义指令可以让我们快速实现图片预览的交互效果。 指令中通过监听 mouseenter 和 mouseleave 事件展示和隐藏图片预览,使用 closest 方法获取 img 元素的父容器,并在其上添加预览图片。

2. v-uppercase

通过 v-uppercase 自定义指令,实现「将文本自动转成大写功能」。 指令实现:

export default {created(el, binding) {el.innerHTML = binding.value.toUpperCase();},update(el, binding) {el.innerHTML = binding.value.toUpperCase();},
};

注册指令:

import { createApp } from "vue";
import vUppercase from "./directives/vUppercase";
import App from "./App.vue";
const app = createApp(App);// 注册指令
app.directive("uppercase", vUppercase);app.mount("#app");

使用指令:

<p v-uppercase>hello</p>

在页面上显示的是 “HELLO” 文本。v-uppercase 自定义指令在 created 和 update 钩子中调用了 toUpperCase() 方法将文本转换为大写,并更新 innerHTML

3. v-resize

通过 v-resize 自定义指令,实现「监听窗口宽度变化」,执行回调方法的功能。 指令实现:

export default {mounted(el, binding) {const callback = binding.value;window.addEventListener("resize", () => {callback(el.offsetWidth);});},
};

注册指令:

import { createApp } from "vue";
import vResize from "./directives/vResize";
import App from "./App.vue";
const app = createApp(App);// 注册指令
app.directive("resize", vResize);app.mount("#app");

使用指令:

<script setup lang="ts">
const onResize = (width) => {console.log(width);
};
</script><template><div v-resize="onResize">宽度</div>
</template>

v-resize 自定义指令会在窗口尺寸发生变化时,调用绑定的回调函数,并传入元素的 offsetWidth 值。在方法 onResize 中,我们可以根据元素的新的宽度 width 进行相应处理,例如:

  • 调整样式

  • 调用 API 重新获取数据

  • 重新布局页面等

这些指令比较简单,但在实际项目中使用却非常广泛,我们可以运用相同思路编写其他常用的指令,例如:

  • v-scroll 滚动事件指令;

  • v-mouseenter / v-mouseleave 鼠标进入/离开事件指令;

  • v-longpress 长按事件指令;

这可以很好的帮助我们简化代码并提高开发效率。

🖌️ 渲染函数中如何使用

1. 概念介绍

如果要在 Vue3 渲染函数中使用自定义指令,就需要使用 [withDirectives](https://vuejs.org/api/render-function.html#withdirectives "withDirectives")函数,其函数签名如下:

function withDirectives(vnode: VNode, // 需要绑定自定义指令的元素directives: DirectiveArguments
): VNode;// 自定义指令数组,数组形式:[Directive, value, argument, modifiers]
// 如果不需要,可以省略数组的尾元素。
type DirectiveArguments = Array<| [Directive]| [Directive, any]| [Directive, any, string]| [Directive, any, string, DirectiveModifiers]
>;

简单的使用示例:

import { h, withDirectives } from "vue";// 一个自定义指令
const pin = {mounted() {/* ... */},updated() {/* ... */},
};// <div v-pin:top.animate="200"></div>
const vnode = withDirectives(h("div"), [[pin, 200, "top", { animate: true }]]);

2. 使用示例

以 v-focus 自定义指令为例,可以按照以下步骤实现:

  1. 导入 withDirectives 和自定义指令函数:

import { withDirectives } from "vue";
import { focus } from "./directives";
  1. 在渲染函数中使用 withDirectives 函数,并按顺序传递参数:

const vnode = h("input", {type: "text",modelValue: "example",onInput: (event) => {// ...},
});const app = {render() {return withDirectives(vnode, [[focus, true]]);},
};

这个示例代码中的 vnode 是一个 input 元素的虚拟节点,focus 是 v-focus 自定义指令的函数,true 是传递给自定义指令的参数数组,表示在元素插入文档后自动聚焦。

📚 总结

本文介绍了 Vue.js 3.x 中自定义指令的基本使用方法,包括自定义指令函数的定义和注册、指令函数中的参数和钩子函数等内容。自定义指令是 Vue.js 框架的一个非常重要的扩展,开发者可以根据自己的需求自定义指令来简化开发工作、提高开发效率。 希望本文对您学习 Vue.js 自定义指令有所帮助。

📖 学习资料

以下是一些我个人认为不错 Vue3 自定义指令的学习资料:

  1. Vue.js 官方文档:自定义指令[4]

Vue.js 官方文档是学习 Vue.js 自定义指令的最佳入门资料,其中包括了自定义指令的定义、注册和钩子函数等方面的内容,以及一些实际应用的示例。

  1. Vue Mastery: Vue 3 Custom Directives[5]

Vue Mastery 是一个非常优秀的 Vue.js 在线教育平台,他们的 Vue 3 Custom Directives 课程是一份非常棒的学习资料,其中详细介绍了 Vue.js 3.x 中自定义指令的使用方法和实践技巧。

  1. Vue 3 Directives: A Comprehensive Guide In Depth[6]

介绍了 Vue.js 3.x 中指令的使用方法和实践技巧。该文章从指令的基础知识入手,详细介绍了 Vue.js 中内置指令和自定义指令的使用方法,并通过实际应用场景和示例来说明指令的作用和用法。

Reference

[1] Vue.js:https://vuejs.org/

[2] 自定义指令:https://vuejs.org/guide/reusability/custom-directives.html

[3] Hook Arguments:https://vuejs.org/guide/reusability/custom-directives.html#directive-hooks

[4] Vue.js 官方文档:自定义指令:https://vuejs.org/guide/reusability/custom-directives.html

[5] Vue Mastery: Vue 3 Custom Directives:https://www.vuemastery.com/courses/vue-3-essentials/custom-directives

[6] Vue 3 Directives: A Comprehensive Guide In Depth:https://www.sciredev.com/blog/vue-3-directives-guide-in-depth

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 在小程序添加公司官网访问
  • 使用 Vue 2 搭建后台管理系统
  • 学习计算机网络(五)——ICMP协议
  • 仕考网:考外省公务员可以调回本地吗?
  • 保研考研机试攻略:第三章——数学(2)
  • uView的u-notice-bar组件横向滚动不生效问题解决
  • 《AI视频类工具之八——​ 快剪辑》
  • Android 使用 PatternsCompat.EMAIL_ADDRESS 判断邮箱、IP地址、域名格式是否正确
  • 基于SpringBoot框架的能源管理系统源代码(100%开源无加密)
  • Linux系统之部署轻量级Markdown文本编辑器
  • 来了...腾讯内推的软件测试面试PDF 文档(共107页)
  • AOP实现日志记录需求
  • 【Python】 Python Schedule 模块:轻量级的定时任务调度库
  • docker 镜像站
  • Qt QLabel标签制作弹框效果,3s后缓慢自动消失
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • cookie和session
  • go语言学习初探(一)
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • JS函数式编程 数组部分风格 ES6版
  • Laravel 实践之路: 数据库迁移与数据填充
  • php的插入排序,通过双层for循环
  • SSH 免密登录
  • Vue ES6 Jade Scss Webpack Gulp
  • vue2.0项目引入element-ui
  • 二维平面内的碰撞检测【一】
  • 高度不固定时垂直居中
  • 给初学者:JavaScript 中数组操作注意点
  • 回流、重绘及其优化
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 面试总结JavaScript篇
  • 你真的知道 == 和 equals 的区别吗?
  • 容器服务kubernetes弹性伸缩高级用法
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 通过git安装npm私有模块
  • 延迟脚本的方式
  • 一个项目push到多个远程Git仓库
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • ​Linux Ubuntu环境下使用docker构建spark运行环境(超级详细)
  • ​力扣解法汇总1802. 有界数组中指定下标处的最大值
  • #AngularJS#$sce.trustAsResourceUrl
  • #QT(串口助手-界面)
  • $(function(){})与(function($){....})(jQuery)的区别
  • (¥1011)-(一千零一拾一元整)输出
  • (1)(1.13) SiK无线电高级配置(五)
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (day 12)JavaScript学习笔记(数组3)
  • (javaweb)Http协议
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (编译到47%失败)to be deleted
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐