在开发Vue项目时,大多数人都会使用到Vue内置的一些指令,例如v-model、v-if等,在使用的时候不知道有没有想过自己也来实现一个指令呢。本文就以Vue3项目为基础,从原理、方法到实际案例、注意事项,尽可能细致的讲解如何自定义指令。
转发自:https://www.jb51.net/article/251168.htm
前言
我们需要明白为什么需要自定义一个指令,其实就是想更加简洁地重复使用操作DOM的逻辑,这就和组件化和组合式函数差不多。
不管是Vue内置的指令还是自定义的指令,都有类似于组件的生命周期,我们可以在不同的生命周期完成不同的逻辑操作,并绑定到组件元素上,这样就产生了自定义指令。在Vue3中,我们有三种方式可以定义指令:
-
如果是在<script setup>定义组件内的指令,有一个语法糖可以使用:任何以v开头的驼峰式命名的变量都可以被用作一个自定义指令,然后在模板中使用。举一个简单的例子:在输入框渲染后自动聚焦
1
2
3
4
5
6
7
8
9
10
<script setup>
// 在模板中启用 v-focus
const vFocus = {
mounted: (el) => el.focus()
}
</script>
<template>
<input v-focus />
</template>
-
如果是使用选项式,则自定义指令需要在
directives
选项中注册。同上一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
export
default
{
setup() {},
directives: {
// 指令名
focus: {
// 生命周期
mounted(el) {
// 处理DOM的逻辑
el.focus();
},
}
}
}
</script>
<template>
<input v-focus />
</template>
实现的效果也是和上一个例子一样。
-
除了注册组件内指令,我们还可以自定义全局指令,这样在所有的组件中都可以使用该指令
1
2
3
4
5
6
7
8
9
10
11
// main.js
import { createApp } from
'vue'
import App from
'./App.vue'
const app = createApp(App)
app.directive(
'focus'
, {
mounted(el) {
el.focus();
}
})
app.mount(
'#app'
)
实现效果也是一样的。
这三种方式我们选择最后一种,其他两种方式可以按照类似的方式实现。
生命周期
指令的生命周期和组件的生命周期类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
注意指令没有beforeCreated
钩子。
- created:在绑定元素的属性前,或者事件监听器应用前调用
- beforeMount:在元素被插入到DOM前调用,例如我们想要实现输入框的自动聚焦,就不能在beforeMount钩子中实现
- mounted:在绑定元素的父组件以及自己的所有子节点都挂载完毕后调用,这个时候DOM已经渲染出来,我们实现输入框自动聚焦也是在这个钩子函数中实现
- beforeUpdate:绑定元素的父组件更新前调用
- updated:在绑定元素的父组件以及自己的所有子节点都更新完毕后调用
- beforeUnmount:绑定元素的父组件卸载前调用
- unmounted:绑定元素的父组件卸载后调用
每个钩子函数都有对应的参数,接下来继续看钩子参数。
钩子的参数
指令是为了能重用对DOM的操作逻辑,因此指令参数可以有1-4个参数,其中必需的参数就是当前绑定的DOM元素。
语法:
created(el, binding, vnode, preVnode) {}
参数比较多,我们一个一个来学习。
-
el
:指令绑定到的DOM元素,可以用于直接操作当前元素,默认传入钩子的就是el参数,例如我们开始实现的focus
指令,就是直接操作的元素DOM -
binding
:这是一个对象,包含以下属性:value
:在元素上使用指令时,传递给指令的值。例如:<div v-reverse="'hello'"></div>
,传递给reserve
指令的值就是hello
,我们可以拿到值并做相关处理oldValue
:之前的值,一般用于beforeUpate
和updated
钩子函数中,例如:beforeUpdate(el, {oldValue: ''})
arg
:传递给指令的参数,非必需,例如<div v-reverse:foo="'hello'"></div>
,那么传递给指令的参数就是foo
modifiers
:一个由修饰符构成的对象,例如<div v-reverse.foo.bar="'hello'"></div>
,那么该修饰符对象为{foo: true, bar: true}
,我们经常使用到的事件修饰符,其实和这个差不多。instance
:使用该指令的组件实例,注意不是DOMdir
:指令的定义对象
-
vnode
:绑定元素的地城VNode -
preVnode
:之前的渲染中代表指令所绑定的元素的VNode,一般用于beforeUpate
和updated
钩子函数中
可能看这些参数会一时迷糊,我们来看一个例子:
定义一个可翻转输入框输入的指令,注意钩子函数要选择beforeUpdate
1 2 3 4 5 6 |
|
在模板中使用:输入框输入值,div
会显示反转后的值
1 2 3 4 5 6 7 8 |
|
简化形式
我们在写指令的时候,可以具体指定在哪些钩子中执行一些逻辑。有时候指令的钩子不止一个,但是又是重复的逻辑操作时,重复写一遍代码显然有点不够优雅。在Vue中,如果我们在自定义指令时,需要在mounted
和updated
中实现相同的行为,并且不关心其他钩子的情况,那么我们开可以采用简写:
1 2 3 4 |
|
对象字面量
我们之前的例子中,传递给指令的值只有一个,如果我们想给指令传入多个值应该怎么操作呢?很简单,传入一个字面量对象即可,可以直接在模板中声明,也可以使用响应式对象,在使用时binding.value
就是一个对象了,而不是一个普通的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
对应的,我们的指令也要小小的修改一下:
1 |
|
实现的效果还是和上面的保持一致。
在组件上使用指令
在元素上直接使用指令,我们可以在指令中操作DOM,这个已经没有问题了。那如果在组件上使用指令会怎样呢?组件其实就是把一些DOM元素封装起来,Vue3和Vue2不同,Vue3的模板中可以不止一个根节点。
我们新建一个Reverse.vue
,以便后续作为组件引入。