在前几篇文章中,我们学习了代码编译--转成--生成的过程。今天,我们将聚焦于指令系统——这个 Vue 中强大的声明式功能。从内置指令(v-if、v-for、v-model)到自定义指令,我们将深入它们的编译原理和运行时实现。
前言:指令的本质
指令是 Vue 模板中带有 v- 前缀的特殊属性。它本质上是一种声明式的语法糖,让我们能够在模板中直接操作 DOM 元素。
<!-- 使用指令 -->
<input v-model="message" />
<div v-if="visible">条件渲染</div>
<div v-custom:arg.modifier="value">自定义指令</div>
指令的注册方式
全局注册
const app = createApp(App);
app.directive('focus', {
mounted(el) {
el.focus();
}
});
app.directive('color', {
mounted(el, binding) {
el.style.color = binding.value;
},
updated(el, binding) {
el.style.color = binding.value;
}
});
局部注册
export default {
directives: {
focus: {
mounted(el) {
el.focus();
}
},
color: {
mounted(el, binding) {
el.style.color = binding.value;
},
updated(el, binding) {
el.style.color = binding.value;
}
}
}
}
import { directive } from 'vue';
export default {
setup() {
const vFocus = {
mounted(el) {
el.focus();
}
};
return { vFocus };
}
}
组件注册原理
// 指令注册的内部实现
function createDirective(name, definition) {
// 规范化指令定义
if (typeof definition === 'function') {
// 函数简写形式
definition = {
mounted: definition,
updated: definition
};
}
return {
name,
...definition
};
}
// 全局注册表
const globalDirectives = new Map();
app.directive = function(name, definition) {
if (definition === undefined) {
// 获取指令
return globalDirectives.get(name);
} else {
// 注册指令
globalDirectives.set(name, createDirective(name, definition));
return this;
}
};
指令生命周期钩子
完整的钩子函数
const myDirective = {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
console.log('created', binding);
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {
console.log('beforeMount', binding);
},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {
console.log('mounted', binding);
el.focus();
},
// 在包含组件的 VNode 更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {
console.log('beforeUpdate', binding);
},
// 在包含组件的 VNode 及其子组件的 VNode 更新后调用
updated(el, binding, vnode, prevVnode) {
console.log('updated', binding);
},
// 在绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {
console.log('beforeUnmount', binding);
},
// 在绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {
console.log('unmounted', binding);
}
};
binding 对象的属性
const binding = {
value: 'directive value', // 指令绑定的值
oldValue: 'old value', // 更新前的值
arg: 'argName', // 指令参数
modifiers: { // 修饰符对象
prevent: true,
stop: true
},
instance: componentInstance, // 组件实例
dir: directiveDefinition, // 指令定义对象
// 在 Vue 3.4+ 中新增
modifiersKeys: ['prevent', 'stop'] // 修饰符数组
};
组件钩子函数的调用时机
编译阶段的指令处理
指令的 AST 表示
我们来看一个比较复杂的自定义指令的例子:
<div v-custom:arg.mod1.mod2="value"></div>
这个例子对应的 AST 节点如下:
const elementNode = {
type: 'Element',
tag: 'div',
props: [
// 普通属性
{ name: 'class', value: 'container' },
// 指令
{
type: 'Directive',
name: 'custom',
arg: 'arg',
modifiers: ['mod1', 'mod2'],
value: 'value',
exp: {
type: 'Expression',
content: 'value'
}
}
]
};
指令的编译转换
/**
* 指令转换插件
*/
const transformDirective = (node, context) => {
if (node.type !== 'Element') return;
if (!node.props) node.props = [];
// 收集指令
const directives = [];
for (let i = node.props.length - 1; i >= 0; i--) {
const prop = node.props[i];
if (prop.type === 'Directive') {
directives.push(prop);
node.props.splice(i, 1); // 从props中移除
}
}
if (directives.length === 0) return;
// 为节点添加指令信息
node.directives = directives.map(dir => ({
name: dir.name,
arg: dir.arg,
modifiers: dir.modifiers,
value: dir.value,
exp: dir.exp
}));
};
/**
* 内置指令的转换
*/
const transformBuiltInDirectives = (node, context) => {
if (!node.directives) return;
for (const dir of node.directives) {
switch (dir.name) {
case 'if':
transformVIf(node, dir, context);
break;
case 'for':
transformVFor(node, dir, context);
break;
case 'model':
transformVModel(node, dir, context);
break;
case 'show':
transformVShow(node, dir, context);
break;
case 'on':
transformVOn(node, dir, context);
break;
case 'bind':
transformVBind(node, dir, context);
break;
// 自定义指令会保留,运行时处理
}
}
};
指令的代码生成
/**
* 生成指令的运行时代码
*/
const genDirective = (dir, context) => {
const { name, arg, modifiers, value } = dir;
// 处理参数
const argStr = arg ? `'${arg}'` : 'null';
// 处理修饰符
const modifiersObj = {};
if (modifiers) {
for (const mod of modifiers) {
modifiersObj[mod] = true;
}
}
// 生成指令对象
return {
name: `'${name}'`,
value: `() => ${value}`,
arg: argStr,
modifiers: JSON.stringify(modifiersObj)
};
};
/**
* 生成节点上的所有指令
*/
const genDirectives = (node, context) => {
if (!node.directives || node.directives.length === 0) return '';
const dirs = node.directives.map(dir => genDirective(dir, context));
return `directives: [${dirs.map(d => `{${Object.entries(d).map(([k, v]) => `${k}: ${v}`).join(', ')}}`).join(', ')}]`;
};
运行时的指令调用
指令调度器
/**
* 运行时指令管理器
*/
class DirectiveManager {
constructor() {
this.directives = new Map(); // 全局指令
this.instances = new WeakMap(); // 元素上的指令实例
}
/**
* 注册指令
*/
register(name, definition) {
this.directives.set(name, definition);
}
/**
* 获取指令定义
*/
get(name) {
return this.directives.get(name);
}
/**
* 在元素上应用指令
*/
applyDirectives(el, vnode) {
const { directives } = vnode;
if (!directives) return;
const instances = [];
for (const dir of directives) {
const definition = this.get(dir.name);
if (!definition) {
console.warn(`指令 ${dir.name} 未注册`);
continue;
}
// 创建指令实例
const instance = {
dir: definition,
binding: this.createBinding(dir, vnode),
vnode
};
instances.push(instance);
// 调用 created 钩子
if (definition.created) {
definition.created(el, instance.binding, vnode);
}
}
this.instances.set(el, instances);
}
/**
* 创建 binding 对象
*/
createBinding(dir, vnode) {
return {
value: dir.value ? dir.value() : undefined,
oldValue: undefined,
arg: dir.arg,
modifiers: dir.modifiers || {},
instance: vnode.component,
dir: this.get(dir.name)
};
}
/**
* 更新指令
*/
updateDirectives(oldVNode, newVNode) {
const el = newVNode.el;
const oldInstances = this.instances.get(el) || [];
const newDirectives = newVNode.directives || [];
// 创建新实例的映射
const newInstances = [];
const newDirMap = new Map();
for (const dir of newDirectives) {
newDirMap.set(dir.name, dir);
}
// 更新现有指令
for (const oldInstance of oldInstances) {
const newDir = newDirMap.get(oldInstance.dir.name);
if (newDir) {
// 指令仍然存在,更新 binding
const oldBinding = oldInstance.binding;
const newBinding = this.createBinding(newDir, newVNode);
newBinding.oldValue = oldBinding.value;
// 调用 beforeUpdate
if (oldInstance.dir.beforeUpdate) {
oldInstance.dir.beforeUpdate(el, newBinding, newVNode, oldInstance.vnode);
}
// 更新实例
oldInstance.binding = newBinding;
oldInstance.vnode = newVNode;
newInstances.push(oldInstance);
newDirMap.delete(oldInstance.dir.name);
} else {
// 指令被移除,调用 beforeUnmount
if (oldInstance.dir.beforeUnmount) {
oldInstance.dir.beforeUnmount(el, oldInstance.binding, oldInstance.vnode);
}
}
}
// 添加新指令
for (const [name, dir] of newDirMap) {
const definition = this.get(name);
if (!definition) continue;
const instance = {
dir: definition,
binding: this.createBinding(dir, newVNode),
vnode: newVNode
};
// 调用 created
if (definition.created) {
definition.created(el, instance.binding, newVNode);
}
newInstances.push(instance);
}
this.instances.set(el, newInstances);
}
/**
* 触发指令钩子
*/
invokeHook(el, hookName, ...args) {
const instances = this.instances.get(el);
if (!instances) return;
for (const instance of instances) {
const hook = instance.dir[hookName];
if (hook) {
hook(el, instance.binding, ...args);
}
}
}
}
// 创建全局指令管理器
const directiveManager = new DirectiveManager();
与渲染器的集成
/**
* 在渲染器中集成指令
*/
class Renderer {
patch(oldVNode, newVNode, container) {
// ... 其他patch逻辑
if (oldVNode && newVNode && oldVNode.el === newVNode.el) {
// 更新指令
directiveManager.updateDirectives(oldVNode, newVNode);
}
}
mountElement(vnode, container, anchor) {
const el = document.createElement(vnode.type);
vnode.el = el;
// 在挂载前调用指令钩子
directiveManager.applyDirectives(el, vnode);
// ... 其他挂载逻辑
// 挂载后调用 mounted
directiveManager.invokeHook(el, 'mounted');
}
unmount(vnode) {
const el = vnode.el;
// 调用 beforeUnmount
directiveManager.invokeHook(el, 'beforeUnmount', vnode);
// ... 卸载逻辑
// 调用 unmounted
directiveManager.invokeHook(el, 'unmounted');
}
}
内置指令的编译实现
常见内置指令
| 内置指令 | 编译处理 | 运行时 | 示例 |
|---|---|---|---|
| v-if | 转为条件表达式 | 条件渲染 | <div v-if="show"> |
| v-for | 转为renderList | 循环渲染 | <li v-for="item in list"> |
| v-model | 拆分为value+事件 | 双向绑定 | <input v-model="text"> |
| v-show | 转为style控制 | 切换display | <div v-show="visible"> |
| v-on | 转为事件绑定 | 事件监听 | <button @click="fn"> |
| v-bind | 转为属性绑定 | 属性更新 | <div :class="cls"> |
| 自定义指令 | 保留指令信息 | 调用钩子 | <div v-custom> |
v-if 的编译
function transformVIf(node, dir, context) {
// 将元素转换为条件节点
node.type = 'Conditional';
node.condition = dir.value;
node.consequent = node;
// 查找相邻的 v-else-if 和 v-else
let current = node;
while (current.next) {
const nextNode = current.next;
const elseDir = nextNode.directives?.find(d => d.name === 'else-if' || d.name === 'else');
if (elseDir) {
if (elseDir.name === 'else-if') {
// 转换为条件分支
current.alternate = {
type: 'Conditional',
condition: elseDir.value,
consequent: nextNode
};
current = current.alternate;
} else {
// v-else
current.alternate = nextNode;
}
// 移除指令标记
nextNode.directives = nextNode.directives?.filter(d => d.name !== 'else-if' && d.name !== 'else');
} else {
break;
}
}
}
/**
* 生成 v-if 代码
*/
function genVIf(node) {
if (node.type !== 'Conditional') return;
let code = `ctx.${node.condition} ? `;
code += genNode(node.consequent);
code += ' : ';
if (node.alternate) {
if (node.alternate.type === 'Conditional') {
code += genVIf(node.alternate);
} else {
code += genNode(node.alternate);
}
} else {
code += 'null';
}
return code;
}
v-show 的编译
function transformVShow(node, dir, context) {
// v-show 只是添加 style 控制
if (!node.props) node.props = [];
const styleProp = node.props.find(p => p.name === 'style');
if (styleProp) {
// 合并现有 style
styleProp.value = `[${styleProp.value}, ctx.${dir.value} ? null : { display: 'none' }]`;
} else {
// 添加 style 属性
node.props.push({
name: 'style',
value: `ctx.${dir.value} ? null : { display: 'none' }`
});
}
// 移除 v-show 指令
node.directives = node.directives?.filter(d => d.name !== 'show');
}
/**
* 生成 v-show 代码(在 props 中体现)
*/
function genVShow(node) {
// v-show 已经在 props 中处理,这里不需要额外生成
return genNode(node);
}
v-model 的编译
function transformVModel(node, dir, context) {
const value = dir.value;
const modifiers = dir.modifiers || [];
// 根据元素类型生成不同的事件和属性
let propName = 'modelValue';
let eventName = 'onUpdate:modelValue';
if (node.tag === 'input') {
if (modifiers.includes('number')) {
// v-model.number
return genNumberModel(value);
} else if (modifiers.includes('trim')) {
// v-model.trim
return genTrimModel(value);
}
} else if (node.tag === 'select') {
propName = 'modelValue';
eventName = 'onUpdate:modelValue';
} else if (node.tag === 'textarea') {
propName = 'modelValue';
eventName = 'onUpdate:modelValue';
}
// 添加 props
if (!node.props) node.props = [];
// 添加 value 绑定
node.props.push({
name: propName,
value: `ctx.${value}`
});
// 添加事件绑定
node.props.push({
name: eventName,
value: genUpdateHandler(value, modifiers)
});
}
/**
* 生成更新处理器
*/
function genUpdateHandler(value, modifiers) {
let handler = `$event => ctx.${value} = $event`;
if (modifiers.includes('number')) {
handler = `$event => ctx.${value} = parseFloat($event)`;
} else if (modifiers.includes('trim')) {
handler = `$event => ctx.${value} = $event.trim()`;
}
if (modifiers.includes('lazy')) {
handler = handler.replace('$event', '$event.target.value');
}
return handler;
}
/**
* 生成数字输入模型
*/
function genNumberModel(value) {
return {
type: 'Directive',
name: 'bind',
arg: 'value',
value: `ctx.${value}`
}, {
type: 'Directive',
name: 'on',
arg: 'input',
value: `$event => ctx.${value} = $event.target.value ? parseFloat($event.target.value) : ''`
};
}
/**
* 生成修剪模型
*/
function genTrimModel(value) {
return {
type: 'Directive',
name: 'bind',
arg: 'value',
value: `ctx.${value}`
}, {
type: 'Directive',
name: 'on',
arg: 'blur',
value: `$event => ctx.${value} = $event.target.value.trim()`
};
}
v-for 的编译
function transformVFor(node, dir, context) {
// 解析 v-for 表达式 "item in list"
const match = dir.value.match(/(.*?) in (.*)/);
if (!match) return;
const [, alias, source] = match;
// 转换为 For 节点
node.type = 'For';
node.source = source.trim();
node.alias = alias.trim();
node.children = node.children || [];
// 添加 key 处理
const keyProp = node.props?.find(p => p.name === 'key' || p.name === ':key');
if (!keyProp) {
// 自动添加 key 建议
console.warn('v-for 应该提供 key 属性');
}
// 移除 v-for 指令
node.directives = node.directives?.filter(d => d.name !== 'for');
}
/**
* 生成 v-for 代码
*/
function genVFor(node) {
if (node.type !== 'For') return;
const { source, alias, children } = node;
return `renderList(ctx.${source}, (${alias}, index) => {
return ${genNode(children[0])}
})`;
}
自定义指令的编译处理
自定义指令的保留
/**
* 处理自定义指令
*/
function transformCustomDirective(node, context) {
if (!node.directives) return;
// 保留自定义指令,运行时处理
node.customDirectives = node.directives.filter(dir => {
return !['if', 'for', 'model', 'show', 'on', 'bind'].includes(dir.name);
});
// 移除已处理的指令
node.directives = node.directives.filter(dir => {
return ['if', 'for', 'model', 'show', 'on', 'bind'].includes(dir.name);
});
}
/**
* 生成自定义指令代码
*/
function genCustomDirectives(node, context) {
if (!node.customDirectives?.length) return '';
const dirs = node.customDirectives.map(dir => {
const { name, arg, modifiers, value } = dir;
return {
name: `'${name}'`,
value: `() => ${value}`,
arg: arg ? `'${arg}'` : 'null',
modifiers: JSON.stringify(modifiers || {})
};
});
return `directives: [${dirs.map(d =>
`{${Object.entries(d).map(([k, v]) => `${k}: ${v}`).join(', ')}}`
).join(', ')}]`;
}
指令的参数和修饰符
/**
* 解析指令参数和修饰符
*/
function parseDirective(name) {
// 例如:v-on:click.prevent.stop
const parts = name.split(':');
const dirName = parts[0];
let arg = parts[1] || '';
let modifiers = [];
// 解析修饰符
if (arg.includes('.')) {
const argParts = arg.split('.');
arg = argParts[0];
modifiers = argParts.slice(1);
}
return {
name: dirName,
arg,
modifiers
};
}
/**
* 生成修饰符处理代码
*/
function genModifiers(modifiers) {
const obj = {};
for (const mod of modifiers) {
obj[mod] = true;
}
return JSON.stringify(obj);
}
事件修饰符的实现
常用事件修饰符
通用事件修饰符
| 修饰符 | 作用 | 典型使用场景 |
|---|---|---|
| .stop | 阻止事件冒泡。 | 防止点击一个内部的按钮意外触发了外层容器的点击事件。 |
| .prevent | 阻止事件的默认行为。 | 自定义表单提交逻辑,或自定义链接行为。 |
| .capture | 使用事件捕获模式。 | 当你希望父元素能比子元素更早地捕获到事件时使用。 |
| .self | 只有当 event.target 是当前元素自身时,才触发事件处理函数。 | 严格区分是点击了元素本身还是其内部子元素的场景。 |
| .once | 事件将只会触发一次。 | 一次性操作,如首次点击的引导、支付按钮等,防止重复提交。 |
| .passive | 告诉浏览器你不想阻止事件的默认行为,从而提升性能。尤其适用于移动端的滚动事件(touchmove),能让滚动更流畅。 | 提升滚动性能,通常用于改善移动端设备的滚屏体验。 |
注:修饰符可以串联使用,比如
@click.stop.prevent会同时阻止冒泡和默认行为。但需要注意顺序,因为相关代码会按顺序生成。
按键修饰符
按键修饰符专门用于监听键盘事件,方便监听按下了哪个键。Vue 为最常用的按键提供了别名,我们可以直接使用:
- .enter (回车键)
- .tab (制表键)
- .delete (捕获“删除”和“退格”键)
- .esc (退出键)
- .space (空格键)
- .up / .down / .left / .right (方向键)
鼠标按键修饰符
指定由特定鼠标按键触发的事件:
- .left (鼠标左键)
- .right (鼠标右键)
- .middle (鼠标滚轮键)
运行时的事件处理
/**
* 运行时事件绑定处理
*/
class EventManager {
constructor() {
this.eventHandlers = new WeakMap();
}
/**
* 绑定事件
*/
addEventListener(el, eventName, handler, options) {
// 解析事件选项
let useCapture = false;
let isPassive = false;
if (eventName.includes('!')) {
useCapture = true;
eventName = eventName.replace('!', '');
}
if (eventName.includes('~')) {
isPassive = true;
eventName = eventName.replace('~', '');
}
const eventOptions = {
capture: useCapture,
passive: isPassive
};
// 存储事件处理器
if (!this.eventHandlers.has(el)) {
this.eventHandlers.set(el, new Map());
}
const handlers = this.eventHandlers.get(el);
handlers.set(eventName, { handler, options: eventOptions });
// 绑定事件
el.addEventListener(eventName, handler, eventOptions);
}
/**
* 更新事件
*/
updateEventListener(el, eventName, newHandler) {
const handlers = this.eventHandlers.get(el);
if (!handlers) return;
const old = handlers.get(eventName);
if (old) {
el.removeEventListener(eventName, old.handler, old.options);
}
if (newHandler) {
this.addEventListener(el, eventName, newHandler.handler, newHandler.options);
}
}
}
手写实现:完整指令系统
/**
* 完整指令编译器
*/
class DirectiveCompiler {
constructor() {
this.builtInDirectives = new Set(['if', 'for', 'model', 'show', 'on', 'bind']);
}
/**
* 编译模板中的指令
*/
compile(template) {
// 1. 解析AST
const ast = this.parse(template);
// 2. 转换AST
this.transform(ast);
// 3. 生成代码
const code = this.generate(ast);
return code;
}
/**
* 解析模板
*/
parse(template) {
// 简化的解析逻辑
const ast = {
type: 'Root',
children: []
};
// 解析元素和指令
const elementRegex = /<(\w+)([^>]*)>/g;
const directiveRegex = /v-(\w+)(?::(\w+))?(?:\.(\w+))?="([^"]*)"/g;
// ... 解析逻辑
return ast;
}
/**
* 转换AST
*/
transform(node) {
if (node.type === 'Element') {
// 提取指令
const directives = [];
if (node.attributes) {
for (const attr of node.attributes) {
const match = attr.name.match(/^v-(\w+)(?::(\w+))?(?:\.([\w.]+))?$/);
if (match) {
const [_, name, arg, modifiersStr] = match;
const modifiers = modifiersStr ? modifiersStr.split('.') : [];
directives.push({
name,
arg,
modifiers,
value: attr.value,
exp: {
type: 'Expression',
content: attr.value
}
});
// 移除原始属性
node.attributes = node.attributes.filter(a => a !== attr);
}
}
}
if (directives.length > 0) {
node.directives = directives;
// 处理内置指令
for (const dir of directives) {
if (this.builtInDirectives.has(dir.name)) {
this.processBuiltInDirective(node, dir);
}
}
// 保留自定义指令
node.customDirectives = directives.filter(
dir => !this.builtInDirectives.has(dir.name)
);
}
// 递归处理子节点
if (node.children) {
for (const child of node.children) {
this.transform(child);
}
}
}
}
/**
* 处理内置指令
*/
processBuiltInDirective(node, dir) {
switch (dir.name) {
case 'if':
this.processVIf(node, dir);
break;
case 'for':
this.processVFor(node, dir);
break;
case 'model':
this.processVModel(node, dir);
break;
case 'show':
this.processVShow(node, dir);
break;
case 'on':
this.processVOn(node, dir);
break;
case 'bind':
this.processVBind(node, dir);
break;
}
}
/**
* 处理 v-if
*/
processVIf(node, dir) {
node.type = 'Conditional';
node.condition = dir.value;
node.consequent = { ...node };
delete node.consequent.directives;
delete node.consequent.customDirectives;
}
/**
* 处理 v-for
*/
processVFor(node, dir) {
const match = dir.value.match(/(.*?) in (.*)/);
if (match) {
node.type = 'For';
node.alias = match[1].trim();
node.source = match[2].trim();
node.iterator = node;
delete node.iterator.directives;
delete node.iterator.customDirectives;
}
}
/**
* 处理 v-model
*/
processVModel(node, dir) {
if (!node.props) node.props = [];
node.props.push({
name: 'modelValue',
value: `ctx.${dir.value}`
});
node.props.push({
name: 'onUpdate:modelValue',
value: this.genUpdateHandler(dir)
});
}
/**
* 处理 v-show
*/
processVShow(node, dir) {
if (!node.props) node.props = [];
const styleProp = node.props.find(p => p.name === 'style');
if (styleProp) {
styleProp.value = `[${styleProp.value}, ctx.${dir.value} ? null : { display: 'none' }]`;
} else {
node.props.push({
name: 'style',
value: `ctx.${dir.value} ? null : { display: 'none' }`
});
}
}
/**
* 处理 v-on
*/
processVOn(node, dir) {
if (!node.props) node.props = [];
const eventName = dir.arg;
let handler = `ctx.${dir.value}`;
// 应用修饰符
if (dir.modifiers) {
handler = this.applyModifiers(handler, dir.modifiers);
}
node.props.push({
name: `on${eventName.charAt(0).toUpperCase() + eventName.slice(1)}`,
value: handler
});
}
/**
* 处理 v-bind
*/
processVBind(node, dir) {
if (!node.props) node.props = [];
node.props.push({
name: dir.arg,
value: `ctx.${dir.value}`
});
}
/**
* 应用修饰符
*/
applyModifiers(handler, modifiers) {
for (const mod of modifiers) {
switch (mod) {
case 'stop':
handler = `$event => { $event.stopPropagation(); ${handler}($event) }`;
break;
case 'prevent':
handler = `$event => { $event.preventDefault(); ${handler}($event) }`;
break;
case 'once':
handler = `once(${handler})`;
break;
}
}
return handler;
}
/**
* 生成更新处理器
*/
genUpdateHandler(dir) {
let handler = `$event => ctx.${dir.value} = $event`;
if (dir.modifiers) {
if (dir.modifiers.includes('number')) {
handler = `$event => ctx.${dir.value} = parseFloat($event)`;
}
if (dir.modifiers.includes('trim')) {
handler = `$event => ctx.${dir.value} = $event.trim()`;
}
if (dir.modifiers.includes('lazy')) {
handler = handler.replace('$event', '$event.target.value');
}
}
return handler;
}
/**
* 生成代码
*/
generate(node) {
if (!node) return 'null';
switch (node.type) {
case 'Root':
return this.generateRoot(node);
case 'Element':
return this.generateElement(node);
case 'Conditional':
return this.generateConditional(node);
case 'For':
return this.generateFor(node);
default:
return 'null';
}
}
/**
* 生成元素代码
*/
generateElement(node) {
const parts = ['createVNode'];
// 标签
parts.push(`'${node.tag}'`);
// 属性
if (node.props) {
const propsObj = {};
for (const prop of node.props) {
propsObj[prop.name] = prop.value;
}
parts.push(JSON.stringify(propsObj));
} else {
parts.push('null');
}
// 子节点
if (node.children) {
const children = node.children.map(child => this.generate(child));
if (children.length === 1) {
parts.push(children[0]);
} else {
parts.push(`[${children.join(', ')}]`);
}
} else {
parts.push('null');
}
// 自定义指令
if (node.customDirectives?.length) {
const dirs = node.customDirectives.map(dir => ({
name: `'${dir.name}'`,
value: `() => ${dir.value}`,
arg: dir.arg ? `'${dir.arg}'` : 'null',
modifiers: JSON.stringify(dir.modifiers || {})
}));
parts.push(JSON.stringify({
directives: dirs
}));
}
return `createVNode(${parts.join(', ')})`;
}
/**
* 生成条件节点
*/
generateConditional(node) {
return `${node.condition} ? ${this.generate(node.consequent)} : null`;
}
/**
* 生成循环节点
*/
generateFor(node) {
return `renderList(ctx.${node.source}, (${node.alias}, index) => ${this.generate(node.iterator)})`;
}
}
结语
理解指令系统,不仅帮助我们更好地使用内置指令,也能创建强大的自定义指令,提升开发效率。指令系统是 Vue 声明式编程的重要体现,它将 DOM 操作封装成声明式的语法,让开发者可以专注于业务逻辑。
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!