开门见山 

vue3全局axios_css

按照惯例,从上帝视角看一下自定义指令在 Vue 3 中发生了哪些改变:

  • 自定义指令的 API 改了名字,名字更贴近组件的生命周期
  • 自定义指令可以通过子组件的 v-bind="$attr"传递

vue3全局axios_vue_02

Vue2.x 的自定义指令 

vue3全局axios_css_03

在 Vue 2 中,自定义指令通过以下可选的钩子去创建:

  • bind:当指令绑定在对应元素时触发。只会触发一次。
  • inserted:当对应元素被插入到 DOM 的父元素时触发。
  • update:当元素更新时,这个钩子会被触发(此时元素的后代元素还没有触发更新)。
  • componentUpdated:当整个组件(包括子组件)完成更新后,这个钩子触发。
  • unbind:当指令被从元素上移除时,这个钩子会被触发。也只触发一次。

来看个例子:

<h1 v-highlight="red">这是一串被高亮为红色的字</h1>
Vue.directive('highlight', {
  bind(el, binding, vnode) {
    el.style.background = binding.value;
  }
});

如上是一个很灵活的做法,通过指令传值的做法,可以供开发者根据使用场景的不同提供不同的参数,以达到不同的效果。

vue3全局axios_css_04

Vue 3 的自定义指令 

vue3全局axios_vue_05

在 Vue 3 中,官方团队将自定义指令的 API 打造的更加“被人所熟悉”。正如你马上会看到的那样,尽管它们和组件的生命周期完全是两回事,但为了更有助于代码的可读性和风格统一,还是选择了比较接近的钩子名称:

  • bind => beforeMount
  • inserted => mounted
  • beforeUpdate: 新的钩子,会在元素自身更新前触发
  • update => 移除!
  • componentUpdated => updated
  • beforeUnmount: 新的钩子,当元素自身被卸载前触发
  • unbind => unmounted

所以,新版的自定义指令大概会长这个样子:

const NewDirective = {
  beforeMount(el, binding, vnode, prevVnode) {},
  mounted() {},
  beforeUpdate() {},
  updated() {},
  beforeUnmount() {},
  unmounted() {},
}

呐,上面的那个例子就会被改写成:

const app = Vue.createApp({});

app.directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value;
  },
});

改了名字之后,是不是更好记了呢?

vue3全局axios_vue_06

细节,注意细节 

vue3全局axios_html_07

Vue 3 是支持 fragments 的,也就是说,我们可以在一个组件中保留多个根节点。

<template>
  <li>你好</li>
  <li>我的</li>
  <li>粉丝</li>
</template>

于是自定义指令就遇到了一个新的问题:自定义指令有多个根节点?

因此,现在的自定义指令已经是 Virtual DOM 节点的一部分了。当组件上挂载了自定义指令时,它的钩子会作为一个外部属性传递进组件内,最终“落地”于组件的 this.$attr

这也就意味着,像下面这种直接在元素上挂载生命周期钩子的写法,即将同样适用于自定义指令的钩子:

<div @vnodeMounted="myHook"></div>

自定义指令向组件内传递 hook 的行为,和组件树中自上而下传递 prop 的行为是一致的。当一个组件给自身内部的元素绑定了 v-bind="$attr" 时,也会将自身的所有自定义指令都传递给自己的子元素。

vue3全局axios_html_08