Compilation processing of custom directives
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:
- Parses directive arguments and modifiers
- Validates whether the directive name is legal
- 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:
v-show
is converted to the_vShow
directive.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:
beforeMount
- Before element insertionmounted
- After element insertionbeforeUpdate
- Before element updateupdated
- After element updatebeforeUnmount
- Before element unmountunmounted
- 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
上一篇:编译器与运行时的协作
下一篇:setup函数的执行时机