不只懂 Vue 语法:什麽是 directive?请示范如何使用 directive?

问题回答

directive(指令)是我们在 Vue 自定义的指令。当我们要重复处理某些工作,例如转换时间呈现的格式的工作,可以使用 directive 来处理。我们可以在全局或局部注册 directive,之後套在 DOM 元素或元件上使用。另外,注册 directive 时,可以设定不同生命周期函式来执行程序码。

以下会再作详细解说。主要会以 Vue 3 作为主要示范例子,但也会补充一些 Vue 2 的做法。

基本用法

在全域或局部注册 directive

全域:

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')

局部(在元件里注册):

在元件中加入 directives 属性:

directives: {
    focus: {
      mounted(el) {
        el.focus()
      }
    }
}

参数可以是物件或函式

注册 directive 时,参数可以是物件或函式。如果你:

  1. 传入物件:可以设定不同生命周期函式。
  2. 传入函式:只在 mounted 执行。

1. 物件作为参数:可设定不同生命周期

先说传入物件的语法。

物件里可以使用的不同生命周期函式。以下只列出两个常用的,其余的可参考这部分的官方文件

app.directive('指令名称', {
    mounted(el, binding) {
        // directive 所绑定的父元件已被挂载
    },
    updated(el, binding) {
        // 元件的 VNode 和它子元件 VNode 更新後
    }
})

这些生命周期函式会接受 4 个参数:

app.directive('focus', {
  mounted(el, binding, vnode, prevNode) {
    ...
  },
})

假设现在建立一个 v-focus 指令,并在指令里传入数值:

<input v-focus:foo="111" type="text">

Vue 里有以下资料:

data() {
    return {
      name: 'Alysa',
      job: 'developer'
    }
}

然後用 console.log 把这个生命周期函式里的所有参数显示出来:

app.directive('focus', {
  mounted(el, binding, vnode, prevNode) {
    console.log(el);
    console.log(binding);
    console.log(vnode);
    console.log(prevNode);
  },
})

结果:

仔细看 binding 物件:

所以,参数的意思是:

  • el: directive 所绑定的 DOM 元素。可以用它来操作 DOM。
  • binding:一个物件,里面会有多个属性。
    例如:
    • value 是在 directive 传入的值,即是 v-focus="111" 的 111。
    • arg 传递给 directive 的参数,即是 v-focus:foo 的 foo。
      instance 可以取得这个 directive 所在的元件的资料。

注意,Vue 2 的生命周期函式的名称与 Vue 3 明显不同
在 Vue 3 的 mounted,在 Vue 2 会变为 inserted。详情参考 Vue 2 的 directive 文件

2. 函式作为参数:预设使用 mounted 生命周期

使用函式的话,就简单多了。如果你不需要设定其他生命周期函式,就建议使用函式作参数。这预设所有程序都在 mounted 里执行。

app.directive('focus', (el) => {
  // 在 mounted 时执行
  el.focus()
})

使用例子

转换时间格式

参考 Mike 老师的文章,想起自己曾经从後端取来的时间格式,并不是画面想呈现的格式。因此前端需要再把时间转换为画面所需的格式。但不可能每次都重复写转换时间的程序码。因此这情况就很适合使用 directive。

假设前端的资料是 ISO 8601 格式的日期。

data() {
    return {
      currentDateTime: new Date().toISOString()
    }
}

但网页想呈现 DD-MM-YYYY 这格式,於是使用 directive 来转换。

先注册一个转换时间的 directive,以下使用 moment 插件来转换格式 :

app.directive('timeFormat', (el, binding) => {
  el.textContent = moment(binding.value).format('DD-MM-YYYY')
})

最後在模版中套上 timeFormat directive 就大功告成:

<p v-timeFormat="currentDateTime"> {{ currentDateTime }} </p>

其他有关 Skeleton 与 directive 的做法

另外,Mike 老师在另一篇文章有解释如何结合使用 directive 和 Skeleton loading 的制作,也推荐大家看看,非常实用。他把 directive 应用在 img 上,在 directive 里透过使用 new Image().onload 的方式来判断图片是否已下载完成,如果是,就把图片的 src 写回 img 的 src 里。否则就继续套用 skeleton 样式。

动态更改样式

有个官方例子很有趣,就是动态修改元素的 position。

先看结果:

做法就是利用 directive 动态绑定此文字的 position 值以及方向。

套用 directive:

<p v-pin:[direction]="pinPadding">
  Direction: {{ direction }} , pinPadding: {{ pinPadding }}
</p>

建立资料:

data() {
    return {
      direction: "top",
      pinPadding: 100,
    };
}

注册 directive:

main.js

app.directive("pin", {
  mounted(el, binding) {
    el.style.position = "fixed";
    el.style[binding.arg] = binding.value + "px";
  },
  updated(el, binding) {
    // 避免 top 与 bottom 或 left 与 right 同时出现
    switch (binding.arg) {
      case "bottom":
        el.style.top = "";
        break;
      case "top":
        el.style.bottom = "";
        break;
      case "right":
        el.style.left = "";
        break;
      case "left":
        el.style.right = "";
        break;
      // no default
    }
    el.style[binding.arg] = binding.value + "px";
  }
});

以上利用 JavaScript 的 element.style.方向 = ...px 来改变元素的位置。另外我跟官方例子不同的是,这例子有另外加上 radio 来选取方向。但考虑到如果 top 和 bottom,或者 right 和 left 值同时出现的话,就会出现数值冲突,以致文字没有动的情况。因此我另外在 updated 函式里加上 switch 来判断。

完整程序码

https://codesandbox.io/s/directive-dong-tai-directive-yv1en?file=/src/App.vue

总结

  • directive 是自定义指令,可以全局或局部注册。
  • 注册 directive 时,可传入物件或函式作参数。前者容许我们设定在不同生命周期里执行程序码。後者则只会在 mounted 里执行程序码。

参考资料

[重构倒数第13天] - Vue3定义自己的模板语法
[重构倒数第12天] - Vue3 directive 与 Skeleton 实战组合应用
Dynamic Directive Arguments


<<:  DAY14-React Overview

>>:  Day 11 : 子集 Subsets

Day 01 初见Blazor

笔者接触软件的时间不长,先後接触三种架构,分别为 ASP.NET MVC、ASP.NET Core ...

.Net Core Web Api_笔记18_api结合ADO.NET资料库操作part6_新闻文章表格陈列查询

由前面几个开发方式可以渐渐了解到前後端分离的开发方式 接着我们要进行新闻文章表格陈列 在Contro...

资安这条路 28 - [作业系统] Windows、Linux

Windows安全 帐号密码安全 目前主机上有哪些帐号 net user Windows 使用者帐号...

卡夫卡的藏书阁【Book2】- 学习资源介绍和Kafka架构微介绍

居高临下远眺百塔之城布拉格的高堡区,卡夫卡的衣冠冢就藏身其中 就让每天一点的卡夫卡陪伴我们熬过这3...

[Day 03]取得Nonce与HashID以产出Sign - [C#]丰收款API必备前置作业(二)

首先来个永丰官方的文件图片作开场吧! (图一:由商户->永丰正向发动的所需参数) 而我们今天要...