• 开始时间:2019-04-09
  • 目标主要版本:3.x
  • 引用 issue:N/A
  • 实现的 PR:N/A

摘要

  • 重新设计自定义指令的 API,使其与组件的生命周期更好的保持一致。
  • 自定义指令在组件上的使用将会遵循 Attribute Fallthrough Behavior RFC 中讨论的相同规则。它将有子组件通过 v-bind="$attrs" 控制。

基本范例

之前

  1. const MyDirective = {
  2. bind(el, binding, vnode, prevVnode) {},
  3. inserted() {},
  4. update() {},
  5. componentUpdated() {},
  6. unbind() {}
  7. }

之后

  1. const MyDirective = {
  2. beforeMount(el, binding, vnode, prevVnode) {},
  3. mounted() {},
  4. beforeUpdate() {},
  5. updated() {},
  6. beforeUnmount() {}, // new
  7. unmounted() {}
  8. }

动机

使得自定义指令的钩子名称与组件的生命周期更加一致。

具体设计

钩子重命名

现有的钩子被重新命名,以更好的映射到组件的生命周期,并进行进了一些时序调整。传递给钩子的参数保持不变。

  • 新的 create(在 vnode 的 props 应用到 DOM 节点之前调用)
  • bind 变为 beforeMount(在 vnode 的 props 应用到 DOM 节点之后调用)
  • inserted 变为 mounted(在子节点被插入到 DOM 节点,并且 DOM 节点本身被插到父节点之后被调用)
  • 新的 beforeUpdate(在元素自身被更新之前调用)
  • 移除 update,该用 updated
  • componentUpdated 变为 updated(在元素本身和其子元素被更新之后调用)
  • 新的 beforeUpdated
  • unbind 改为 unmounted

在组件上的用法

在 3.0 中,有了 fragment 的支持,组件可能存在多个根节点。当在一个多根节点的组件上使用自定义指令时,这就产生了一个问题。

为了解释自定义指令在 3.0 中如何组件上工作的细节,我们首先要理解自定义指令在 3.0 中是如何被编译的。对于像这样的指令:

  1. <div v-foo="bar"></div>

大致会被编译为:

  1. const vFoo = resolveDirective('foo')
  2. return withDirectives(h('div'), [
  3. [vFoo, bar]
  4. ])

其中 vFoo 将是用户写的指令对象,它包含了像 mountedupdated 钩子。

withDirective 返回一个克隆的 VNode,用户钩子被包装并作为 vnode 生命周期注入(更多细节见 Render Function API Changes):

  1. {
  2. onVnodeMounted(vnode) {
  3. // call vFoo.mounted(...)
  4. }
  5. }

因此,自定义指令被完全包含在 VNode 的数据中。当一个自定义指令被用在一个组件上时,这些 **onVnodeXXX** 钩子被作为无关的 props 传递给组件,并最终进入到 **this.$attrs**

这也意味着可以像这样在模版中直接 hook 一个元素的生命周期,这在自定义指令过于复杂的情况下很方便。

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

这与 vue/rfcs #26 中讨论的属性破坏性行为是一致的。所以,组件上的自定义指令的规则将和其他外在属性一样:由子组件决定在哪里以及是否应用它。当子组件在内部元素上使用 v-bind="$attrs",它也将应用在它上面使用的任何自定义指令。

缺点

N/A

备选方案

N/A

采纳策略

  • 重命名应该很容易在 compat 构建中得到支持。
  • Codemod 也应该直截了当。
  • 对于在组件上使用指令,Attribute Fallthrough Behavior 中讨论的关于未使用的 $attrs 的警告也应该适用。

没有解决的问题

N/A