自定义指令的编译处理
自定义指令的编译处理
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中的指令节点进行标准化处理:
- 解析指令参数和修饰符
- 验证指令名称是否合法
- 处理动态参数情况
动态参数示例:
<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对部分内置指令有特殊优化:
v-show
转换为_vShow
指令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`)
}
}
}
典型调用顺序:
beforeMount
- 元素插入前mounted
- 元素插入后beforeUpdate
- 元素更新前updated
- 元素更新后beforeUnmount
- 元素卸载前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
上一篇:编译器与运行时的协作
下一篇:setup函数的执行时机