阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 自定义指令的编译处理

自定义指令的编译处理

作者:陈川 阅读数:12707人阅读 分类: Vue.js

自定义指令的编译处理

Vue3中自定义指令的编译过程涉及模板解析、AST转换、代码生成等环节。当编译器遇到v-前缀的属性时,会识别为指令并进行特殊处理,最终生成可执行的渲染函数代码。

指令的模板解析阶段

在模板解析阶段,编译器通过正则表达式匹配指令:

const directiveRE = /^v-([^.:]+)(?:\.([^.:]+))?(?::([^.:]+))?(?:\.([^.:]+))?$/

对于如下模板代码:

<div v-my-directive:arg.modif="value"></div>

解析后会生成对应的AST节点属性:

{
  type: 1, // 元素节点
  tag: 'div',
  attrsList: [
    {
      name: 'v-my-directive:arg.modif',
      value: 'value'
    }
  ],
  directives: [
    {
      name: 'my-directive',
      rawName: 'v-my-directive:arg.modif',
      arg: 'arg',
      modifiers: { modif: true },
      value: 'value'
    }
  ]
}

指令的AST转换处理

编译器会对AST中的指令节点进行标准化处理:

  1. 解析指令参数和修饰符
  2. 验证指令名称是否合法
  3. 处理动态参数情况

动态参数示例:

<div v-my-directive:[dynamicArg].modif="value"></div>

对应的AST节点:

{
  directives: [
    {
      name: 'my-directive',
      rawName: 'v-my-directive:[dynamicArg].modif',
      arg: {
        type: 4, // 动态表达式
        content: 'dynamicArg'
      },
      modifiers: { modif: true },
      value: {
        type: 4,
        content: 'value'
      }
    }
  ]
}

指令的代码生成

在代码生成阶段,编译器会将指令转换为_withDirectives函数调用:

// 生成的渲染函数代码
import { resolveDirective as _resolveDirective } from 'vue'
import { withDirectives as _withDirectives } from 'vue'

const _directive_my_directive = _resolveDirective("my-directive")

return _withDirectives(
  (_openBlock(), _createBlock("div")),
  [
    [
      _directive_my_directive,
      value,
      "arg",
      { modif: true }
    ]
  ]
)

内置指令的特殊处理

Vue对部分内置指令有特殊优化:

  1. v-show转换为_vShow指令
  2. v-model根据元素类型生成不同代码

v-model在输入框上的处理:

// 生成的代码
import { vModelText as _vModelText } from 'vue'

return _withDirectives(
  (_openBlock(), _createBlock("input", {
    "onUpdate:modelValue": $event => (value = $event)
  }, null, 8 /* PROPS */, ["onUpdate:modelValue"])),
  [
    [_vModelText, value]
  ]
)

自定义指令的运行时处理

运行时通过withDirectives函数应用指令:

function withDirectives(vnode, directives) {
  const internalInstance = currentRenderingInstance
  if (internalInstance === null) {
    return vnode
  }
  
  const instance = internalInstance.proxy
  const bindings = vnode.dirs || (vnode.dirs = [])
  
  for (let i = 0; i < directives.length; i++) {
    const [dir, value, arg, modifiers = {}] = directives[i]
    let binding = {
      dir,
      instance,
      value,
      oldValue: void 0,
      arg,
      modifiers
    }
    bindings.push(binding)
  }
  
  return vnode
}

指令的生命周期调用

在patch过程中,会根据指令绑定调用相应钩子:

function callHook(dir, hook, vnode, prevVNode) {
  const fn = dir.dir[hook]
  if (fn) {
    try {
      fn(vnode.el, dir, vnode, prevVNode)
    } catch (e) {
      handleError(e, vnode.context, `directive ${dir.dir.name} ${hook} hook`)
    }
  }
}

典型调用顺序:

  1. beforeMount - 元素插入前
  2. mounted - 元素插入后
  3. beforeUpdate - 元素更新前
  4. updated - 元素更新后
  5. beforeUnmount - 元素卸载前
  6. unmounted - 元素卸载后

动态指令参数处理

动态参数需要额外处理表达式变化:

<div v-my-directive:[dynamicArg]="value"></div>

生成的代码包含参数更新逻辑:

return _withDirectives(
  (_openBlock(), _createBlock("div")),
  [
    [
      _directive_my_directive,
      value,
      _ctx.dynamicArg
    ]
  ]
)

在更新阶段会对比新旧参数:

if (hasDynamicArg || (oldArg !== newArg)) {
  callHook(binding, 'beforeUpdate', vnode, prevVNode)
  binding.oldValue = binding.value
  binding.value = value
  binding.arg = newArg
  callHook(binding, 'updated', vnode, prevVNode)
}

指令与组件生命周期的协调

指令生命周期与组件生命周期保持同步:

const setupRenderEffect = (instance, initialVNode) => {
  const componentUpdateFn = () => {
    if (!instance.isMounted) {
      // beforeMount hooks
      callDirectiveHooks(instance, 'beforeMount')
      
      // mounted hooks
      queuePostRenderEffect(() => {
        callDirectiveHooks(instance, 'mounted')
      }, parentSuspense)
    } else {
      // beforeUpdate hooks
      callDirectiveHooks(instance, 'beforeUpdate')
      
      // updated hooks
      queuePostRenderEffect(() => {
        callDirectiveHooks(instance, 'updated')
      }, parentSuspense)
    }
  }
}

指令的合并策略

相同指令多次使用时,Vue会按特定顺序合并:

<div v-dir="1" v-dir="2"></div>

处理逻辑:

function mergeDirectives(prev, next) {
  const res = Object.create(null)
  if (prev) {
    for (const key in prev) {
      res[key] = prev[key]
    }
  }
  if (next) {
    for (const key in next) {
      res[key] = next[key]
    }
  }
  return res
}

最终后定义的指令会覆盖先定义的指令。

指令的性能优化

Vue对静态指令有特殊优化标记:

function processDirectives(node, context) {
  if (node.directives) {
    if (!hasDynamicKeys(node.directives)) {
      node.patchFlag |= PatchFlags.OPTIMIZED
    }
  }
}

静态指令在diff阶段会被跳过:

if (vnode.patchFlag & PatchFlags.OPTIMIZED) {
  // 跳过静态指令比较
  return
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌