Custom directives for Vue3

foreword

Vue provides a variety of instructions for us to use, such as v-model, v-bind, etc. It can be said that instructions are one of the important function points of Vue. In addition to some built-in instructions in Vue, Vue also allows us to define instructions by ourselves. I believe that friends who have learned Vue should know more or less about custom instructions. Custom instructions are very useful in some scenarios. It can provide us with Save excess work.

But there are some differences between the custom instructions in Vue3 and Vue2. Today we will focus on how to use the custom instructions in Vue3?

  1. What are custom directives?

Built-in directives:

In Vue, such as v-if, v-for, v-on, etc. are called built-in instructions, they all start with v-, we can use them globally without registration, built-in instructions provide great For our convenience, for example, the v-for command allows us to quickly loop out many dom elements, etc., similar to the following code:

<ul>
  <li v-for="index in 10">小猪课堂</li>
</ul>

Custom directive:

Although Vue has provided many built-in instructions for us to use, people are greedy and are always not satisfied with the status quo. So the official allows us to customize the command, the custom command is more flexible, we can use any name to name the custom command, but our custom designation still needs to start with v-, such as v-focus, v-resize and so on.

For example, below we use a custom command

<input v-focus />

2. Register a custom instruction

In Vue, if we define a component, we need to register it before it can be used. The principle is similar to the custom instruction, we need to register the custom instruction before it can be used.

Components can be registered as local components and global components. Similarly, our custom instructions are also divided into global registration and local registration . Next, we will learn how to register custom instructions.

2.1 Global Registration

When our custom instruction adopts global registration, we can use this custom instruction in any component, and the way of global registration is also very simple.

code show as below:

// main.ts
import { createApp } from "vue";
import App from "./App.vue";
const app = createApp(App);
app.directive("focus", {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
  },
  // 在元素被插入到 DOM 前调用
  beforeMount() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted() {},
  // 绑定元素的父组件更新前调用
  beforeUpdate() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated() {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount() {},
  // 绑定元素的父组件卸载后调用
  unmounted() {},
});
app.mount("#app");

In the above code, we registered a global custom directive with the help of the directive method provided by Vue3. This method receives two parameters: the directive name and the directive hook function object.

The hook function object has the same life cycle as the component, which is also quite different from the custom instructions in Vue2. It is also very simple to understand these hook functions: we all know that custom instructions act on DOM elements, so a series of operations from binding to DOM elements to changing DOM elements, etc., correspond to different hooks Functions, such as when a DOM element is inserted into the document, hook functions such as mounted of the custom instruction will be executed.

Call the custom command registered globally, the code is as follows:

<input type="text" v-focus>

We can call it from any component.

2.2 Partial registration

Usually when we have many components that need to use the same custom instruction, we will take the way of global registration of custom instructions. But many times we may only need to use custom instructions inside a certain component. At this time, there is no need to register globally. We can use local registration.

Since there are two ways of writing <script setup> and <script> in Vue3, the registration ways of custom instructions corresponding to the two ways of writing are different.

Register in <script setup>:

<script setup lang="ts">
// 在模板中启用 v-focus
const vFocus = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {},
  // 在元素被插入到 DOM 前调用
  beforeMount() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted() {},
  // 绑定元素的父组件更新前调用
  beforeUpdate() {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated() {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount() {},
  // 绑定元素的父组件卸载后调用
  unmounted() {},
};
</script>

In the above code, we directly defined a vFocus variable, and this variable is actually our custom command. Some friends may wonder why a variable becomes a custom command?

In fact, in Vue3, as long as the variable named with the camelcase starting with the lowercase letter v can be used as a custom instruction, for example, vFocus in the above code can be used in the template through the v-focus instruction form.

Use in <script>:

When <script setup> is not used in Vue3, we can register custom instructions in the form of attributes, which is very similar to Vue2.

code show as below:

export default {
  setup() {
    /*...*/
  },
  directives: {
    // 在模板中启用 v-focus
    focus: {
      /* 指令钩子函数 */
    }
  }
}

3. Detailed hook function and parameters

In the previous section, we only briefly talked about how to register custom instructions globally and locally in Vue3. But our focus should be on the hook function in the custom instruction and the parameters received by the hook function. After all, we use the custom instruction to fulfill some requirements.

3.1 Hook function

The hook function in the custom directive is almost the same as the lifecycle function in Vue3, which is one of the major differences from the custom directive in Vue2.

If you understand the life cycle function in Vue3, then it is very simple for you to understand the hook function of the custom instruction. You can simply understand it like this: Vue3's lifecycle function is relative to an application or a component, while the hook function of a custom component is relative to a DOM element and its child elements.

Let's test the hook function of the custom command, simply modify the App.vue file, register a local custom command v-focus, and then bind the custom command to the input element.

code show as below:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <input type="text" v-focus />
</template>
<script setup lang="ts">
// 在模板中启用 v-focus
const vFocus = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created() {
    console.log("我是钩子函数:created");
  },
  // 在元素被插入到 DOM 前调用
  beforeMount() {
    console.log("我是钩子函数:beforeMount");
  },
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted() {
    console.log("我是钩子函数:mounted");
  },
  // 绑定元素的父组件更新前调用
  beforeUpdate() {
    console.log("我是钩子函数:beforeUpdate");
  },
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated() {
    console.log("我是钩子函数:updated");
  },
  // 绑定元素的父组件卸载前调用
  beforeUnmount() {
    console.log("我是钩子函数:beforeUnmount");
  },
  // 绑定元素的父组件卸载后调用
  unmounted() {
    console.log("我是钩子函数:unmounted");
  },
};
</script>

Output result:

The above code is very simple, it just prints a sentence in the hook function. When we refresh the page, 3 hook functions will be executed, because the DOM element we bind the custom command has not changed, so the other hook functions have not changed. implement. We can try to modify some codes and try to trigger other hook functions.

code show as below:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div v-focus>
    <input type="text" />
    <div>{
    
    { message }}</div>
  </div>
  <button @click="changeMsg">修改message</button>
</template>
<script setup lang="ts">
import { ref } from "vue";


let message = ref<string>("小猪课堂");
const changeMsg = () => {
  message.value = "张三";
};
// 在模板中启用 v-focus
const vFocus = {
  // 钩子函数
};
</script>

The page shows:

When we click the button to modify the message, we will find that the hook function in the custom instruction is executed.

Output result:

In order to facilitate everyone's understanding, let's summarize the execution timing of each hook function:

created: Called before the attribute of the bound element or before the event listener is applied.

beforeMount: Called before the element is inserted into the DOM.

mounted: Called after the parent component of the binding element and all its child nodes are mounted.

beforeUpdate: Called before the parent component of the bound element is updated.

updated: Called after the bound element's parent component and all of its own child nodes have been updated.

beforeUnmount: Called before the parent component of the bound element is unmounted.

unmounted: Called after the parent component of the bound element is unmounted.

3.2 Detailed Explanation of Hook Function Parameters

The purpose of our use of custom instructions is to flexibly manipulate the DOM and process our business logic in the hook function, so Vue3 passes some parameters that we may use to the hook function, and the custom instructions in Vue3 and Vue2 are passed parameters are similar.

In the previous code, we can see the following piece of code:

created(el, binding, vnode, prevVnode) {}

The created hook function in the above code receives 4 parameters. What do these 4 parameters represent? Let's take a look together.

Detailed parameter explanation:

el: The element the directive is bound to. This can be used to directly manipulate the DOM.

binding: an object that contains the following properties

value: The value passed to the directive. For example in v-my-directive="1 + 1" the value is 2.

oldValue: the previous value, only available in beforeUpdate and updated. It is available whether or not the value has changed.

arg: Arguments (if any) passed to the directive. For example in v-my-directive:foo the parameter is "foo".

modifiers: An object containing modifiers, if any. For example in v-my-directive.foo.bar the modifier object is { foo: true, bar: true }.

instance: The component instance that uses this directive.

dir: The definition object of the directive.

vnode: represents the underlying VNode of the bound element.

prevNode: The VNode representing the element bound to the command in the previous rendering. Only available in beforeUpdate and updated hooks.

We can print these parameters to understand better.

code show as below:

created(el: HTMLElement, binding: DirectiveBinding, vnode: VNode, prevVnode: VNode) {
  console.log(el);
  console.log(binding);
  console.log(vnode);
  console.log(prevVnode);
},

Output result:

4. Command value passing

When we explain the parameters of the hook function, there is a binding parameter, which is an object with many attributes, and some of these attributes are the values ​​​​passed by the instruction.

When we use the v-if or v-for command, it is usually followed by an = sign, such as v-if="boolean" and so on. The value after the equal sign is the value we pass to the command.

The same is true for custom directives, which can also pass values ​​like built-in directives.

code show as below:

<div v-focus:foo="'小猪课堂'">
</div>

Next, let's print out the binding in the hook function.

Output result:

You can see that the value in the binding object is the value after the = sign, and the foo in v-focus:foo is the arg in binging.

Seeing this, let’s recall the usage of some built-in instructions: v-bind:on="", v-model="". It can be seen from here that the arg of the instruction is dynamic, so the arg of our custom instruction can also be dynamic, such as the following writing method.

code show as below:

<div v-example:[arg]="value"></div>

The arg in the above code is a variable.

5. Command abbreviation

从前几节可以看出,自定义指令提供了非常多的钩子函数,但是在实际的需求开发中,我们可能并不需要这么多钩子函数,所以Vue3和Vue2一样,都提供了自定义指令的简写形式给我们。比如我们经常用到的可能就是mounted和updated钩子函数了,所以官方直接针对这两种情况间提供了简写方法。

代码如下:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div v-focus="'#ccc'">
    <input type="text" />
    <div>{
    
    { message }}</div>
  </div>
  <button @click="changeMsg">修改message</button>
</template>
<script setup lang="ts">
import { DirectiveBinding, ref} from "vue";
let message = ref<string>("小猪课堂");
const changeMsg = () => {
  message.value = "张三";
};
// 在模板中启用 v-focus
const vFocus = (el: HTMLElement, binding: DirectiveBinding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.backgroundColor = binding.value;
};
</script>

上段代码中我们省去了自定义指令中的所有钩子函数,直接简写为了一个箭头函数。我们在div上绑定了自定义指令,并且传入了一个颜色参数。

输出结果:

当刷新页面时,div的背景色发生了变化。

6.自定义指令案例

防抖是我们在开发中很大概率会遇到的,通常小伙伴们都是自己写一个防抖函数,或者直接调用某些库的防抖函数来实现防抖,我们这次使用自定义指令的方式来方便的实现防抖。

代码如下:

<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <button v-debounce="changeMsg">修改message</button>
</template>
<script setup lang="ts">
import { DirectiveBinding, ref, VNode } from "vue";
let message = ref<string>("小猪课堂");
const changeMsg = () => {
  message.value = "张三";
  console.log('改变messag');
};
const vDebounce = (el: HTMLElement, binding: DirectiveBinding) => {
  let timer: null | number = null;
  el.addEventListener("click", () => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      binding.value(); // value = changeMsg
    }, 1000);
  });
};
</script>

输出结果:

上段代码中我们点击按钮可以更改message,如果采用@click的方式来触发方法,那么不断点击将会不停的触发方法。如果采用我们自定义指令v-debounce的形式来触发方法,那么就会实现方法的防抖,一定时间内只会触发一次。

可以把v-debounce自定义指令注册到全局,因为很多地方都需要防抖。

Guess you like

Origin blog.csdn.net/weixin_53841730/article/details/129587003