如何在Vue3中实现自定义指令【转发】

在开发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

app.directive('focus', {

  created() {

    console.log('created');

  },

  beforeMount() {

    console.log('beforeMount');

  },

  mounted() {

    console.log('mounted');

  },

  beforeUpdate() {

    console.log('beforeUpdate');

  },

  updated() {

    console.log('updated');

  },

  beforeUnmount() {

    console.log('beforeUnmount');

  },

  unmounted() {

    console.log('unmounted');

  }

})

注意指令没有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:之前的值,一般用于beforeUpateupdated钩子函数中,例如:beforeUpdate(el, {oldValue: ''})
    • arg:传递给指令的参数,非必需,例如<div v-reverse:foo="'hello'"></div>,那么传递给指令的参数就是foo
    • modifiers:一个由修饰符构成的对象,例如<div v-reverse.foo.bar="'hello'"></div>,那么该修饰符对象为{foo: true, bar: true},我们经常使用到的事件修饰符,其实和这个差不多。
    • instance:使用该指令的组件实例,注意不是DOM
    • dir:指令的定义对象
  • vnode:绑定元素的地城VNode

  • preVnode:之前的渲染中代表指令所绑定的元素的VNode,一般用于beforeUpateupdated钩子函数中

可能看这些参数会一时迷糊,我们来看一个例子:

定义一个可翻转输入框输入的指令,注意钩子函数要选择beforeUpdate

1

2

3

4

5

6

app.directive('reserve', {

  beforeUpdate(el, binding) {

    console.log(binding);

    el.innerText = binding.value ? binding.value.split('').reverse().join('') : '';

  }

})

在模板中使用:输入框输入值,div会显示反转后的值

1

2

3

4

5

6

7

8

<script setup>

import {ref} from 'vue'

let hello = ref('')

</script>

<template>

  <input v-focus v-model="hello" />

  <div v-reserve:foo.bar="hello"></div>

</template>

 

简化形式

我们在写指令的时候,可以具体指定在哪些钩子中执行一些逻辑。有时候指令的钩子不止一个,但是又是重复的逻辑操作时,重复写一遍代码显然有点不够优雅。在Vue中,如果我们在自定义指令时,需要在mountedupdated中实现相同的行为,并且不关心其他钩子的情况,那么我们开可以采用简写:

1

2

3

4

app.directive('color', (el, binding) => {

    // 这将会在mounted和updated时调用

    el.style.color = binding.value;

})

对象字面量

我们之前的例子中,传递给指令的值只有一个,如果我们想给指令传入多个值应该怎么操作呢?很简单,传入一个字面量对象即可,可以直接在模板中声明,也可以使用响应式对象,在使用时binding.value就是一个对象了,而不是一个普通的值。

1

2

3

4

5

6

7

8

9

10

11

12

13

<script setup>

import {ref, reactive} from 'vue'

let hello = ref('')

const obj = reactive({

  hello: '',

  world: ''

})

</script>

<template>

  <input v-focus v-model="obj.hello" />

  <div v-reserve:foo.bar="obj"></div>

  <!-- <div v-reserve:foo.bar="{hello: obj.hello, world: obj.world}"></div> -->

</template>

对应的,我们的指令也要小小的修改一下:

1

el.innerText = binding.value ? binding.value.hello.split('').reverse().join('') : '';

实现的效果还是和上面的保持一致。

在组件上使用指令

在元素上直接使用指令,我们可以在指令中操作DOM,这个已经没有问题了。那如果在组件上使用指令会怎样呢?组件其实就是把一些DOM元素封装起来,Vue3和Vue2不同,Vue3的模板中可以不止一个根节点。

我们新建一个Reverse.vue,以便后续作为组件引入。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值