【Vue.js】1711- 深入浅出 Vue3 自定义指令

news2024/11/20 12:39:29

a82d680a9e6feba34df20ba7fd83c3f6.jpeg

Vue.js[1] 提供了丰富的指令来简化开发者的工作。除了内置指令外,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 元素上应用自定义的行为」

2. 基础使用

以全局自定义指令为例,通过全局方法 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。仅在 beforeUpdateupdated 钩子中可用。

参数的详细介绍,可以查看文档《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」

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

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 自定义指令可以让我们快速实现图片预览的交互效果。 指令中通过监听 mouseentermouseleave 事件展示和隐藏图片预览,使用 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 自定义指令在 createdupdate 钩子中调用了 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 元素的虚拟节点,focusv-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

往期回顾

#

如何使用 TypeScript 开发 React 函数式组件?

#

11 个需要避免的 React 错误用法

#

6 个 Vue3 开发必备的 VSCode 插件

#

3 款非常实用的 Node.js 版本管理工具

#

6 个你必须明白 Vue3 的 ref 和 reactive 问题

#

6 个意想不到的 JavaScript 问题

#

试着换个角度理解低代码平台设计的本质

b45a315d0c2cf32b730bf4cc1111e7b8.gif

回复“加群”,一起学习进步

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

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

相关文章

HLS 设计数字时钟

绪论 该项目的目标是展示 HLS 在设计数字系统方面的能力。为此&#xff0c;本文展示如何在 HLS 中描述数字时钟。如果有兴趣学习 HLS 编码技术&#xff0c;请参阅&#xff1a; ❝ https://highlevel-synthesis.com/ ❞ ❝ https://www.udemy.com/course/hls-combinational-circ…

2023 年的 5G 和网络安全风险

5G 网络的推出出奇地缓慢。作为一个概念&#xff0c;它于 2016 年推出&#xff0c;但直到 2019 年才在全球范围内推出。 四年后&#xff0c;在大多数国家地区&#xff0c;拥有 5G 设备的人数仍然很少。 不确定采用缓慢背后的原因是负担能力、缺乏必要性还是关于它的严重错误…

【redis】redis集群

这里是redis系列文章之《redis集群》&#xff0c;上一篇文章链接&#xff1a;【redis基础】哨兵_努力努力再努力mlx的博客-CSDN博客 目录 概念 作用 集群算法-分片-槽位slot 槽位与分配的概念及两者的优势 官网介绍分析 槽位 分片 两者的优势 slot槽位映射的三种解决方…

linux eventfd事件通知 比信号量更好用

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e; 目录 前言 概述 原理简介 使用场景 接口说明 头文件 参数说明 代码演示 默认参数 …

1740_使用Python+ImageMagick实现图像的批量压缩

全部学习汇总&#xff1a; GreyZhang/python_basic: My learning notes about python. (github.com) 前些年使用Linux的时候为了能够方便地往网络上上传照片&#xff0c;使用shell ImageMagick的组合进行照片的批量压缩一直觉得比较方便。不过&#xff0c;那时候即使这么简单的…

JMeter从入门到精通--开始你的第一个JMeter脚本

JMeter是一款在国外非常流行和受欢迎的开源性能测试工具&#xff0c;像LoadRunner 一样&#xff0c;它也提供了一个利用本地Proxy Server&#xff08;代理服务器&#xff09;来录制生成测试脚本的功能&#xff0c;但是这个功能并不好用。所以在本文中介绍一个更为常用的方法——…

软考A计划-2023系统架构师-知识点集锦(4/4)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

redis的远程登录配置

目录 服务端&#xff1a; 客户端&#xff1a; 服务端&#xff1a; 步骤一&#xff1a;关闭防火墙 systemctl stop firewalld iptables -F setenforce 0 步骤二&#xff1a;配置/etc/redis.conf配置文件 vim /etc/redis.conf bind 127.0.0.1 改为 bind 0.0.0.0 &#xff08;…

【图书推荐 | 13】前端系列

【赠书活动第十三期 】 图书推荐 本期书籍&#xff1a;前端系列 图书列表&#xff1a; Vue.js核心技术解析Nuxt.js实战Nuxt.js Web开发实战HTML5CSS 从入门到精通Flutter2 开发实例精解Electron项目开发实战 Vue.js核心技术解析 Nuxt.js实战 Nuxt.js Web开发实战 HTML5CSS 从入…

机器鱼的制作分享

1. 运动功能说明 本文示例将实现R330样机机器鱼胸鳍能够灵活的上下摆动的功能。 2. 结构说明 本样机采用舵机模块来进行仿生机器鱼结构的设计。 胸鳍 整机 3. 电子硬件 在这个示例中&#xff0c;我们采用了以下硬件&#xff0c;请大家参考&#xff1a; 主控板 Basra主控板&…

一文搞懂ChatGPT 和 AIGC 到底是什么?【最强科普】

目录&#xff1a; 1.AIGC是什么&#xff1f; 2.ChatGPT是什么&#xff1f; 3.ChatGPT发展的几个阶段&#xff1f; 4.ChatGPT能做什么&#xff1f; 5.ChatGPT的应用场景&#xff1f; 一、AIGC是什么&#xff1f; GC&#xff08;Generated Content&#xff09;&#xff1a…

Java实训日记第六天——2023.6.12

文章目录 一、MyBatis-Plus二、将MyBatis-Plus框架整合到SpringBoot1.导依赖2.在启动类上开扫描3.在application.yml中配关于数据库的连接4.在idea中安装MyBatisX插件5.在idea中连上MySQL6.测试查询所有 3.完善增删改查功能4.逻辑删除5.自动填充 一、MyBatis-Plus 1.为什么有了…

【手撕MyBatis源码】Configuration配置体系

文章目录 Configuration概述Configuration的核心作用与配置来源配置元素元素承载配置文件解析XML文件解析流程注解配置解析 Configuration概述 Configuration 是整个MyBatis的配置体系集中管理中心&#xff0c;前文所说的Executor、StatementHandler、Cache、MappedStatement……

react---react router 5 基本使用

目录 1.路由介绍 2.路由使用 3.路由组件和一般组件 4.Switch 单一匹配 5.解决二级路由样式丢失的问题 6.路由精准匹配和模糊匹配 1.路由介绍 路由是根据不同的 URL 地址展示不同的内容或页面&#xff0c;在 SPA 应用中&#xff0c;大部分页面结果不改变&#xff0c;只改变…

PostgreSQL 对特定类索引的优化,节省磁盘空间百倍 --BRIN 索引应用在生产案例...

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

为生信写的Python简明教程 | 视频5

开源生信 Python教程 生信专用简明 Python 文字和视频教程 源码在&#xff1a;https://github.com/Tong-Chen/Bioinfo_course_python 目录 背景介绍 编程开篇为什么学习Python如何安装Python如何运行Python命令和脚本使用什么编辑器写Python脚本Python程序事例Python基本语法 数…

Pytorch教程:Autograd基础

PyTorch的Autograd特征可以让PyTorch灵活快速的构建机器学习项目。autograd可以实现快速和容易的多重偏微分&#xff08;梯度&#xff09;计算。偏微分计算时反向传播神经网络学习的核心。 autograd的可以在运行时动态追踪计算&#xff0c;这意味着如果模型有决策分支、或者有…

linux 系统服务管理

目录 一、chkconfig 1、列出服务列表 chkconfig --list 2、关闭开启服务 chkconfig 服务名 on/off 3、添加新服务 chkconfig --add 服务文件名 4、删除已有服务 chkconfig --del 服务名 5、系统级别定义&#xff1a; 一、chkconfig chkconfig——centos…

Python学习46:分配学号(python123)

类型&#xff1a;列表元组‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 描述‪‬…

【Java项目】多种方式解决SpringBoot中遇到的控制台中文乱码问题

文章目录 配置JVM参数配置maven参数配置Runner配置Encoding通过配置文件 配置JVM参数 -Dfile.encodingUTF-8配置maven参数 重点就是 <configuration><fork>true</fork><jvmArguments>-Dfile.encodingUTF-8</jvmArguments></configuration>…