Skip to content

RFC:在 Vue 中实现另一种自定义指令 API

字数
791 字
阅读时间
4 分钟

custom directive 是 Vue 中一个非常强大的功能,它允许我们在 DOM 元素上添加自定义行为。在 Vue Vapor 中为了更好的性能,移除了 vDom。我之前的尝试模仿了 vDom 的 custom directive API,结果是这样的模仿会十分消耗性能,并增加代码的复杂度。

为了解决这个问题,我尝试使用另一种方式来实现新的自定义指令 API,在 Vue Vapor 中获得更好的性能并兼容 Vue Core。

思路速记

  • 使用 effectScope 和 effect 现有的生命周期作为触发时机,避免模拟 vDom 行为的性能损耗。
  • 如果不使用相关 API,就避免产生性能损耗。
  • v-show 和 v-model 这样的 API 为确保性能,尽量使用底层定制化实现而不是用户层的自定义指令 API 来实现。
html
<p v-directive="myRef">text</p>
  • created: 在指令声明时执行
  • beforeMount: 在元素所在的 effectScope 执行之后,元素挂载“前”执行
  • mounted: 在元素所在的 effectScope 执行之后,元素挂载“后”执行
  • beforeUpdate: 在绑定的值变化后,元素更新“前”执行,类似 render watch。
  • updated: 在绑定的值变化后,元素更新“后”执行,类似 post watch
  • beforeUnmount: 在元素所在的 effectScope 卸载之前执行
  • unmounted: 在元素所在的 effectScope 卸载之后执行

编写指令

我们可以创建一个 directive 函数。它的作用就把 v-directive="myRef" 里面的 myRef 传递给我们的自定义指令函数。

这个自定义指令函数会在当前这个 node 所在的 effectScope 中被调用。

函数的行为就像 setup 一样,所以开发者可以在里面写类似 watchEffect 这样的代码。setup 的 props 会包含用户传入的值。

js
import { onScopeBeforeMount, onScopeDispose } from 'vue/vapor'
const vShowMap = new WeakMap()

export function vShow(source, node) {
  let value = source()
  let oldValue

  onScopeBeforeMount(() => {
    vShowMap.set(node, node.style.display === 'none' ? '' : node.style.display)
    setDisplay(node, value)
  })

  watchPostEffect(() => {
    let value = source()
    if (!oldValue !== !value)
      setDisplay(node, source)
    oldValue = value
  })

  onScopeDispose(() => {
    setDisplay(node, source())
  })
}

function setDisplay(el, value) {
  el.style.display = value ? vShowMap.get(el)! : 'none'
}

需要注意的是为了实现 beforeMount / mounted,我们需要拓展 effectScope。在其中增加两个 hooks。并在 vapor runtime 中合适的时机调用它。如果没有任何地方用到这两种 hook,则跳过调用。

生成指令运行时

js
function render(ctx) {
  const n0 = t0()
  vShow(() => ctx.ok, n0)
  return n0
}

兼容 Vue Core

可以在现有的 v1 自定义指令 created 钩子的相同时机创建一个 scope 作为 v2 的 setup,然后在 unmounted 钩子中销毁这个 scope 作为 v2 的 onScopeDispose

至于 beforeMount / mounted 钩子,我们可以在现有的 v1 自定义指令中同名 hook 相同时机调用 v2 的 hook。

对值的 update 回调使用 Watch API 实现。

贡献者

页面历史

撰写