Vue自定义指令

在Vue的模板语法中我们除了使用:v-show、v-for、v-model等,Vue 也允许我们来自定义自己的指令。
自定义指令分为两种:

  • 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用;
  • 自定义全局指令:app的 directive 方法,可以在任意组件中被使用;

局部与全局的概念

  • 局部指的是在组件的范围内注册的指令、组件、过滤器、方法等资源,只能在该组件内部使用。这些局部资源仅对该组件及其子组件可见。
  • 全局指的是在整个 Vue 应用程序范围内注册的资源,可以在任何组件中使用。这些全局资源对整个应用程序都是可见的,无需特定的引入或导入。
  • 通过在组件的 directivescomponentsfiltersmethods 等属性中注册资源,可以将资源限定在特定组件范围内(局部)。如果你希望资源可以在所有组件中使用(全局),可以在创建 Vue 实例之前注册这些资源,或者使用 Vue 的 app.directiveapp.componentapp.filter 等全局注册方法。

全局指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 自定义指令</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<p>页面载入时,input 元素自动获取焦点:</p>
<input v-focus>
</div>

<script>
const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
// 当被绑定的元素挂载到 DOM 中时……
mounted(el) {
// 聚焦元素
el.focus()
}
})
app.mount('#app')
</script>
</body>
</html>

这段代码使用 Vue 3 创建了一个应用,并注册了一个全局自定义指令 v-focus。自定义指令的主要功能是在元素挂载到 DOM 中时自动聚焦该元素,这是一个常见的需求,例如在页面加载完成后,输入框自动获取焦点。

代码中的关键部分是自定义指令的定义,它是一个对象,其中包含了一个名为 mounted 的生命周期钩子函数。这个钩子函数在被绑定的元素挂载到 DOM 中时会被调用,传入的参数 el 就是挂载的元素。在这个钩子函数内部,通过 el.focus() 来实现对元素的聚焦操作。

如果你在模板中使用了 v-focus,例如:

1
<input v-focus>

那么这个输入框在页面加载完成后会自动获取焦点,因为它绑定了 v-focus 自定义指令。这是一个简单而有用的自定义指令示例,可以让开发者更方便地实现页面元素的交互行为。

局部指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 局部指令</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<p>页面载入时,input 元素自动获取焦点:</p>
<input v-focus>
</div>

<script>
const app = {
data() {
return {
}
},
directives: {
focus: {
// 指令的定义
mounted(el) {
el.focus()
}
}
}
}

Vue.createApp(app).mount('#app')
</script>
</body>
</html>

上面代码中使用directives来注册局部指令。

钩子函数

在 Vue 3 中,钩子函数是用于在组件的生命周期中执行特定操作的函数。Vue 3 的生命周期钩子函数与 Vue 2 有些不同,以下是一些常用的 Vue 3 钩子函数及其用途:

  • beforeCreate: 在实例初始化之后,数据观测 (data observation) 和 event/watcher 事件配置之前被调用。这个阶段你不能访问到数据和实例中的方法。
  • created: 在实例创建完成后被立即调用。在这个阶段,实例已经完成了数据观测、属性和方法的运算,但是还没有挂载到 DOM 中。你可以在这里执行一些初始化操作,如获取数据、设置默认值等。
  • beforeMount: 在挂载开始之前被调用,相关的 render 函数首次被调用。
  • mounted: 在实例挂载到 DOM 后调用。这是常用于执行初始的 DOM 操作,如获取元素、发送请求等的地方。
  • beforeUpdate: 数据更新时被调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在这里执行一些操作以准备更新。
  • updated: 数据更新后被调用,发生在虚拟 DOM 重新渲染和打补丁之后。
  • beforeUnmount: 在实例销毁之前调用。这是用于清理工作的地方,如取消定时器、取消订阅等。
  • unmounted: 在实例销毁之后调用。在这个阶段,组件已经被销毁,无法再访问组件的数据和方法。

这些钩子函数允许你在组件的不同生命周期阶段执行特定的操作,从而控制组件的行为。每个钩子函数都有其特定的用途,可以根据需要选择使用它们。

实践案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 钩子函数</title>
<script src="https://cdn.staticfile.org/vue/3.2.36/vue.global.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>

<script>
const app =Vue.createApp({
data(){
return{
message: 'hello word'
}
},

//实例生成之前的自动执行的函数
beforeCreate(){
console.log('beforeCreate')
},
//实例生成之后自动执行的函数
created(){
console.log('created')
},

//组件被渲染到页面之前自动执行的函数
beforeMount(){
console.log(document.getElementById('root').innerHTML,'beforeMount')
},

//组件内容被渲染到页面之后自动执行的函数
mounted(){
console.log(document.getElementById('root').innerHTML,'mounted')
},
//当数据发送变化会立即自动执行的函数
beforeUpdate(){
console.log(document.getElementById('root').innerHTML,'beforeUpdate')
},
//当数据发生变化后,页面重新渲染后会自动执行的函数
updated(){
console.log(document.getElementById('root').innerHTML,'updated')

},
//应用失效之前自动执行的函数
beforeUnmount(){
console.log(document.getElementById('root').innerHTML,'beforeUnmount')
},
//应用失效之后,切dom完全销毁后自动执行的函数
unmounted(){
console.log(document.getElementById('root').innerHTML,'unmounted')
},


template: '<div>{{message}}</div>'
}

);

const vm= app.mount('#root')
</script>
</html>
  1. 运行后可以看到控制台输出如下内容,可以看到依次执行了beforeCreate,created、beforeMount、mounted函数。
1
2
3
4
5
6
You are running a development build of Vue.
Make sure to use the production build (*.prod.js) when deploying for production.
VM158:11 beforeCreate
VM158:15 created
VM158:20 beforeMount
VM158:25 <div>hello word</div> mounted
  1. 通过 vm 访问应用实例,然后修改 message 的值。这种方式应该能够成功修改 message 并触发视图的更新,执行之后可以看到依次执行了beforeUpdate,updated函数。
1
2
3
4
vm.message="sutune"
hello_word.html:41 <div>hello word</div> beforeUpdate
hello_word.html:45 <div>sutune2023</div> updated
'sutune2023'
  1. 最后执行unmount操作,依次执行了beforeUnmount,unmounted函数。
1
2
3
app.unmount()
hello_word.html:50 <div>sutune2023</div> beforeUnmount
hello_word.html:54 unmounted

钩子函数参数

el参数

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

1
2
3
4
5
app.directive('focus', {
mounted(el) {
el.focus()
}
})

binding

在 Vue 3 的钩子函数中,可以通过钩子函数的参数来访问 binding 对象。binding 对象包含了一些有用的信息,用于描述指令的行为和上下文。

在 Vue 3 的钩子函数中,binding 对象通常包含以下属性:

  • value: 表达式的值,这是绑定到指令的数据的实际值。
  • instance: Vue 实例,当前指令所在的 Vue 实例。
  • arg: 指令的参数,如果指令有参数的话。例如,在 v-my-directive:foo 中,arg 将是 "foo"
  • modifiers: 一个包含修饰符的对象,如果指令有修饰符的话。例如,在 v-my-directive.foo.bar 中,modifiers 将是 { foo: true, bar: true }

这些属性允许你在钩子函数中访问和操作指令的数据以及上下文信息。例如,你可以通过 binding.value 获取指令的值,然后在钩子函数中进行相应的操作。

以下是一个示例,演示如何在钩子函数中使用 binding 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
app.directive('my-directive', {
beforeMount(el, binding) {
// 访问指令的值
const value = binding.value;

// 访问指令的参数
const arg = binding.arg;

// 访问指令的修饰符
const modifiers = binding.modifiers;

// 访问当前 Vue 实例
const instance = binding.instance;

// 在钩子函数中进行操作
// ...
}
});

通过使用 binding 对象,你可以更灵活地处理指令的行为,根据不同的上下文信息来执行不同的逻辑。这对于创建自定义指令或处理复杂的交互行为非常有用。

vnode与prevNode

在 Vue 3 中,vnodeprevNode 是在自定义指令的钩子函数中用于比较前后两次虚拟节点 (VNode) 的对象。

  • vnode 表示当前正在处理的 VNode,它代表了元素的当前状态。
  • prevNode 表示之前的 VNode,代表了元素的上一个状态。

这些参数在自定义指令的钩子函数中可以用于执行一些 DOM 操作或比较前后两次 VNode 的状态以实现特定的功能。以下是一些常用的钩子函数和它们的参数:

  • bind(el, binding, vnode, prevNode):在元素绑定指令时调用,参数中的 vnodeprevNodenull

  • inserted(el, binding, vnode, prevNode):元素插入 DOM 后调用,此时的 vnode 代表元素的当前状态,prevNodenull

  • update(el, binding, vnode, prevNode):元素更新时调用,vnodeprevNode 分别表示元素的当前和之前的状态。

  • componentUpdated(el, binding, vnode, prevNode):组件更新后调用,vnodeprevNode 分别表示组件的当前和之前的状态。

  • unbind(el, binding, vnode, prevNode):元素解绑指令时调用,vnodenullprevNode 代表元素的最后一个状态。

这些参数可以用于在自定义指令的钩子函数中执行一些逻辑,例如对比前后两次 VNode 的状态,实现特定的 DOM 操作或其他自定义功能。

实践案例

下面这段代码是一个 Vue 3 示例,演示了如何创建一个自定义指令 v-sutune 并在元素上使用它,然后在自定义指令的钩子函数中访问指令的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 自定义指令</title>
<script src="https://cdn.staticfile.org/vue/3.2.31/vue.global.min.js"></script>
</head>
<body>
<div id="app">
<div v-sutune="{ name: 'sutune', url: 'www.sutune.me' }"></div>
</div>

<script>
const app = Vue.createApp({})
app.directive('sutune', (el, binding, vnode) => {
console.log(binding.value.name) // => "sutune"
console.log(binding.value.url) // => "www.sutune.me"
})
app.mount('#app')
</script>
</body>
</html>

具体解析如下:

  1. 创建 Vue 应用实例:

    1
    const app = Vue.createApp({})
  2. 注册一个自定义指令 v-sutune

    1
    2
    3
    4
    app.directive('sutune', (el, binding, vnode) => {
    console.log(binding.value.name) // 输出 "sutune"
    console.log(binding.value.url) // 输出 "www.sutune.me"
    })

    在这里,我们定义了一个 sutune 指令,它的行为由一个函数定义,该函数接受三个参数:el(元素)、binding(绑定对象)和 vnode(虚拟节点)。

  3. 在 HTML 中使用自定义指令:

    1
    2
    3
    <div id="app">
    <div v-sutune="{ name: 'sutune', url: 'www.sutune.me' }"></div>
    </div>

    在这里,我们在一个 <div> 元素上使用了 v-sutune 指令,并传递了一个包含 nameurl 属性的对象作为参数。

最终的效果是,当页面加载时,自定义指令 v-sutune 会在绑定的元素上执行,输出指令参数中的 nameurl 属性值到浏览器的控制台。

1
2
3
4
You are running a development build of Vue.
Make sure to use the production build (*.prod.js) when deploying for production.
VM68:4 sutune
VM68:5 www.sutune.me

参考资料