Vue
在除了提供默认的十几个内置的指令外,还允许开发人员根据实际情况自定义指令,那我们在何时使用它呢?
在Vue的项目中,大多数情况下,你都可以操作数据来修改视图,也就是所谓的操作DOM,但是还是避免不了偶尔要操作原生DOM,当我们需要操作DOM的时候,就可以使用到自定义指令。
当然也能用 $refs
,在未学习自定义指令之前,我们让一个想让页面的输入框自动聚焦,我们可能会怎么做:
<template>
<input ref="input" />
</template>
<script>
export default {
mounted() {
this.$refs.input.focus();
}
}
</script>
复制代码
上面的代码基本能实现我们需要的功能,但是要是有很多页面都需要这个功能,那我们就只能是复制这段代码过去了,而通过自定义指令我们就能回避这种问题,下面就看看如果使用指令,应该怎么做。
Vue.directive('focus', {
bind() {},
inserted(el) {
el.focus()
},
update() {},
componentUpdated() {},
unbind() {}
})
复制代码
我们通过全局的Vue实例注册一个自定义指令,然后通过 v-focus
绑定到需要聚焦的 input 元素上。如果,其他组件或模块也需要聚焦功能,只要简单的绑定此指令即可。
<template>
<input v-focus />
</template>
复制代码
自定义指令能给我们带来极高的便利,而在 Vue2
中给一个指令定义对象可以提供 bind
、inserted
、update
、componentUpdated
、unbind
五个钩子函数。更多详情
但是在新发布的 Vue3
中对指令也做了一些改造,主要就是对其中的钩子函数进行了优化升级,还有一些小的注意点,下面我们就一起来看看。
Vue3中指令的变化
Vue3
对指令的生命周期钩子改造了一翻,使其更像和普通组件钩子一般,更加方便可读和记忆。
app.directive('directiveName', {
// 在绑定元素的 attribute 或事件监听器被应用之前调用, 在指令需要附加须要在普通的 v-on 事件监听器前调用的事件监听器时,这很有用
created() {},
// 当指令第一次绑定到元素并且在挂载父组件之前调用
beforeMount() {},
// 在绑定元素的父组件被挂载后调用
mounted() {},
// 在更新包含组件的 VNode 之前调用
beforeUpdate() {},
// 在包含组件的 VNode 及其子组件的 VNode 更新后调用
updated() {},
// 在卸载绑定元素的父组件之前调用
beforeUnmount() {},
// 当指令与元素解除绑定且父组件已卸载时, 只调用一次
unmounted() {},
});
复制代码
Vue3
改造后的生命周期钩子变成了七个,而且名称变得比较好记了。
Vue3 | Vue2 |
---|---|
created | |
beforeMount | bind |
mounted | inserted |
beforeUpdate | update被移除! |
updated | componentUpdated |
beforeUnmount | |
unmounted | unbind |
注意点
Vue3
中开始支持Fragment,也就是说,我们可以在一个组件中保留多个根节点。
// HelloWorld.vue
<template>
<div>Hello</div>
<div>World</div>
</template>
复制代码
这会要是在一个多根组件上使用自定义指令,指令会被忽略,并且会抛出一个警告。
如果只是单根组件上使用自定义指令依旧和 Vue2
一样指令会应用在最外层节点上。
常见指令
v-copy
对于Web端来说要实现复制内容到剪贴板,一般我们都会直接选择下一个npm依赖来使用,非常方便简单。而与Vue相关的插件,Vue2有vue-clipboard2,Vue3有它的升级版vue-clipboard3。
但这次我们用原生方法来写,保证这个指令不用依赖其他包,而其中最重要的一个方法就是document.execCommand('Copy')
,其作用就是将拷贝当前选中内容到剪贴板。
查了一下,这个API兼容性还行。
但看了一下 MDN 文档记载竟然是个废弃的API,但现在依旧能在各大浏览器上跑,还没删除,只是没形成标准。
这本来是 IE 的私有 API,在 IE9 时被引入,后续的若干年里陆续被 Chrome / Firefix / Opera 等浏览器也做了兼容支持,但始终没有形成标准。
不过不用慌,只要还没删,我们就用它,到时有问题到时再说,哈哈哈。
在写正式代码前,我们先缕缕过程先:
- 首先,使用场景可能是我们点击某一个按钮,就复制了某个内容(目标内容)到剪贴板中了,通过
ctrl+v
能粘贴出来。 - 把内容塞进剪贴板,我们会用到上面提到的
document.execCommand('Copy')
API来实现,但是这里要注意,该API的作用是将当前 选中 的内容拷贝进剪贴板,所以我们必须让我们的目标内容被选中,才能调用该API来完成功能。 - 让内容被选中我们能通过 HTML事件 中的
onselect()
方法来实现,而<input />
标签、<textarea />
标签都能支持该事件。 - 所以我们需要动态创建一个
<textarea />
标签,当然该标签只是个辅助工具,所以要把它移出可视区域外。 - 再将我们的目标内容赋值给它的value属性,将它插入到页面DOM结构中。
- 调用
<textarea />
标签的onselect()
选中值,再通过document.execCommand('Copy')
API把内容复制进剪贴板。 - 最后移除
<textarea />
标签就可以。
具体代码就如下:
app.directive('copy', {
beforeMount(el, binding) {
el.targetContent = binding.value;
el.addEventListener('click', () => {
if(!el.targetContent) return console.warn('没有需要复制的目标内容');
// 创建textarea标签
const textarea = document.createElement('textarea');
// 设置相关属性
textarea.readOnly = 'readonly';
textarea.style.position = 'fixed';
textarea.style.top = '-99999px';
// 把目标内容赋值给它的value属性
textarea.value = el.targetContent;
// 插入到页面
document.body.appendChild(textarea);
// 调用onselect()方法
textarea.select();
// 把目标内容复制进剪贴板, 该API会返回一个Boolean
const res = document.execCommand('Copy');
res && console.log('复制成功,剪贴板内容:' + el.targetContent);
// 移除textarea标签
document.body.removeChild(textarea);
})
},
updated(el, binding) {
// 实时更新最新的目标内容
el.targetContent = binding.value;
},
unmounted(el) {
el.removeEventListener('click', ()=>{})
}
})
复制代码
有时我们在点击复制后,可能需要个回调方法去做其他骚操作,那我们再改造改造代码。
app.directive('copy', {
beforeMount(el, binding) {
el.targetContent = binding.value;
const success = binding.arg;
el.addEventListener('click', () => {
...
res && success ? success(el.targetContent) : console.log('复制成功,剪贴板内容:' + el.targetContent);
...
})
},
...
})