阿里云主机折上折
  • 微信号
Current Site:Index > Compilation processing of custom directives

Compilation processing of custom directives

Author:Chuan Chen 阅读数:12706人阅读 分类: Vue.js

Compilation Processing of Custom Directives

In Vue 3, the compilation process of custom directives involves template parsing, AST transformation, code generation, and other steps. When the compiler encounters an attribute with the v- prefix, it recognizes it as a directive and performs special processing, ultimately generating executable render function code.

Directive Template Parsing Phase

During the template parsing phase, the compiler matches directives using regular expressions:

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

For the following template code:

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

After parsing, the corresponding AST node properties are generated:

{  
  type: 1, // Element node  
  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 Transformation of Directives

The compiler standardizes directive nodes in the AST:

  1. Parses directive arguments and modifiers
  2. Validates whether the directive name is legal
  3. Handles dynamic argument cases

Example of a dynamic argument:

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

Corresponding AST node:

{  
  directives: [  
    {  
      name: 'my-directive',  
      rawName: 'v-my-directive:[dynamicArg].modif',  
      arg: {  
        type: 4, // Dynamic expression  
        content: 'dynamicArg'  
      },  
      modifiers: { modif: true },  
      value: {  
        type: 4,  
        content: 'value'  
      }  
    }  
  ]  
}  

Code Generation for Directives

During the code generation phase, the compiler converts directives into _withDirectives function calls:

// Generated render function code  
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 }  
    ]  
  ]  
)  

Special Handling of Built-in Directives

Vue has special optimizations for certain built-in directives:

  1. v-show is converted to the _vShow directive.
  2. v-model generates different code depending on the element type.

Handling of v-model on an input field:

// Generated code  
import { vModelText as _vModelText } from 'vue'  

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

Runtime Processing of Custom Directives

At runtime, directives are applied via the withDirectives function:

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  
}  

Invocation of Directive Lifecycle Hooks

During the patch process, corresponding hooks are called based on directive bindings:

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`)  
    }  
  }  
}  

Typical invocation sequence:

  1. beforeMount - Before element insertion
  2. mounted - After element insertion
  3. beforeUpdate - Before element update
  4. updated - After element update
  5. beforeUnmount - Before element unmount
  6. unmounted - After element unmount

Handling Dynamic Directive Arguments

Dynamic arguments require additional processing for expression changes:

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

The generated code includes logic for argument updates:

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

During the update phase, old and new arguments are compared:

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

Coordination Between Directives and Component Lifecycle

Directive lifecycle hooks are synchronized with the component lifecycle:

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

Directive Merge Strategy

When the same directive is used multiple times, Vue merges them in a specific order:

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

Processing logic:

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  
}  

The last defined directive will override the earlier ones.

Performance Optimization for Directives

Vue applies special optimization flags to static directives:

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

Static directives are skipped during the diff phase:

if (vnode.patchFlag & PatchFlags.OPTIMIZED) {  
  // Skip comparison for static directives  
  return  
}  

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

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.