在vue 中,官方给我们提供了v-model、v-html 等这些方便好使的指令。这使得我们能在日常开发中大大提高了我们的开发效率。
那么除了官方提供的指令以外,我们同样也可以自定义封装指令。其实指令可以理解为我们经常封装的 js 工具类就行,都是为了方便好使、好管理。只不过是在自定义指令的时候,我们要按照vue官方提供好的方式去分装就行。
下面我们就以日常中比较常用到的【复制文本】和【预览图片】功能为例,记录下我们是如何创建自己的指令的:
1. 创建指令目录文件
个人比较喜欢将项目中的指令,统一创建到一个文件夹下,如在vue2 项目中的 src 目录下创建一个 directive 文件夹用来存放所有的指令代码文件,目录如下图所示:
创建 index.js文件,用来管理所有的指令文件
url: src/directive/index.js
代码如下所示:
/**
* 指令的钩子函数:
* vue2指令文档:https://vuejs.bootcss.com/guide/custom-directive.html
*
* bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
* inserted: 被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
* update: 所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
* componentUpdated: 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
* unbind: 只调用一次,指令与元素解绑时调用。
*/
import copy from "./v-copy";
import imagePreview from "./v-imagePreview";
// 自定义指令
const directives = {
copy,
imagePreview,
};
export default {
// 全局注册多文件指令
install(Vue) {
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key]);
});
},
};
2. 在main.js 中全局引入指令
这边我用到的是全局引入,因为都是些常用的指令。当然,如果你希望是局部引入,那就按需引入就行。
import Directive from "@/directive/index.js"; // 自定义指令
Vue.use(Directive);
3. 开始创建指令
作者这边用到了 vant 的UI组件库,你们可根据自己的项目要求自行替换即可。
vant 的使用具体参照其官方文档 https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart
复制文本指令【v-copy】
url: src/directive/v-copy.js
代码如下:
/**
* 复制指令:v-copy
* 用法:<button v-copy="`这是你要复制的文本`">copyText</button>
*/
// vant 的提示api(可替换成你们的)
// import { Toast } from "vant";
const copy = {
/**
* 指令绑定再DOM时调用,只执行一次
* @param {*} el 指令所绑定的元素,可以用来直接操作 DOM
* @param {*} binding 指令的信息,里面的value为指令绑定的值
*/
bind(el, binding) {
el.$value = binding.value;
// 创建事件-复制文本逻辑
el.copyFun = () => {
if (!el.$value) {
// Toast("暂无复制内容");
console.log("暂无复制内容")
return;
}
const textarea = document.createElement("textarea"); // 手动创建 textarea 文本标签
textarea.value = el.$value; // 设置要复制的值
document.body.appendChild(textarea); // 将 textarea 标签添加到页面中
textarea.select(); // 选中文本框的值
try {
const result = document.execCommand("Copy"); // 复制
if (result) {
// Toast("复制成功"); // 复制成功
console.log("复制成功")
} else {
// Toast("复制失败"); // 复制失败
console.log("复制失败")
}
} catch {
// 浏览器不支持
// Toast("复制失败,请检查浏览器是否兼容");
console.log("复制失败,请检查浏览器是否兼容")
}
document.body.removeChild(textarea); // 移除 textarea 标签
};
el.addEventListener("click", el.copyFun); // 绑定点击事件
},
/* 组件数据更新之后值行 */
componentUpdated(el, binding) {
el.$value = binding.value;
},
/* 与元素解绑时执行 */
unbind(el) {
// console.log("coyp-unbind:", el);
el.removeEventListener("click", el.copyFun);
},
};
export default copy;
预览图片指令【v-imagePreview】
url: src/directive/v-imagePreview.js
代码如下:
/**
* 预览图片 v-imagePreview
* 使用:
*
* 方式一:v-imagePreview:[index]="imageList"
* 参数:
* index:需要预览图片的下标
* imageList:图片数组 如:["https://img.jpg","https://img2.jpg"],
*
* 方式二:(适合动态配置 ImagePreview 的其他属性)
* v-imagePreview="{ images: imageList, startPosition: 1(图片索引) }"
* 参数:
* 传入vant ImagePreview Api 的相关配置属性
*
*/
// 这里使用的是 vant ui组件里的 预览图片api, 可以按你们的项目要求替换
import { ImagePreview } from "vant";
const imagePreview = {
bind(el, binding) {
el.imgList = binding.value; // 图片的值
el.imgIndex = binding.arg || null; // 图片下标
// 创建事件
el.ImagePreview = () => {
// 两种传值方式都可以(具体可参看顶部的使用说明)
if (el.imgIndex) {
// 方式一:(可替换成你们项目的预览图片的方式)
ImagePreview({
images: el.imgList,
startPosition: el.imgIndex,
});
} else {
// 方式二:(可替换成你们项目的预览图片的方式)
ImagePreview(el.imgList);
}
};
el.addEventListener("click", el.ImagePreview); // 绑定点击事件
},
componentUpdated(el, binding) {
// 赋值
el.imgList = binding.value;
el.imgIndex = binding.arg || null; // 图片下标
},
unbind(el) {
el.removeEventListener("click", el.copyFun);
},
};
export default imagePreview;
4. 使用自定义指令
全局引入的话就直接使用即可
<template>
<div>
<!-- v-copy -->
<h2>v-copy的使用</h2>
<van-button v-copy="`这是你要复制的文本`"
type="primary">v-copy复制文本</van-button>
<!-- v-imagePreview -->
<h2>v-imagePreview的使用</h2>
<div class="img-box">
<!-- 预览图片-方式一 -->
<img v-for="(item, index) in imageList"
:key="index"
:src="item"
v-imagePreview:[index]="imageList"
alt="" />
<!-- 预览图片-方式二 -->
<!-- <img v-for="(item, index) in imageList"
:key="index"
:src="item"
v-imagePreview="{ images: imageList, startPosition: index }"
alt="" /> -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
imageList: [
'https://img01.yzcdn.cn/vant/apple-1.jpg',
'https://img01.yzcdn.cn/vant/apple-2.jpg'
]
}
}
}
</script>
<style lang="scss" scoped>
.img-box {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-top: 30px;
img {
width: 100px;
height: 100px;
margin: 0 20px;
}
}
</style>
以上是vue2的自定义指令的方式。
至于vue3的话估计封装自定义指令的方式和钩子函数会稍有不同,具体可以参考下vue3的官方文档。https://www.javascriptc.com/vue3js/guide/custom-directive.html#%E7%AE%80%E4%BB%8B