本文已参与「新人创作礼」活动,一起开启掘金创作之路。
上一节我们介绍了event的parse过程(点击这里查看),这一节我们来分析event的gencode的过程,关于genCode的大致流程,我之前的文章已经介绍过了(点击这里查看),接下来我们开始吧。
event编译之genCode
在codegen流程中会遍历AST树生成字符串代码,这个过程中,会执行genElement解析标签上定义的不同属性:
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.staticRoot && !el.staticProcessed) {
......
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
// el.plain是false,普通元素执行genData,事件处理也会进入这个函数
const data = el.plain ? undefined : genData(el, state)
......
}
......
return code
}
}
当遇到event事件的时候,会执行genData方法:
genData
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
......
// event handlers
if (el.events) {
// AST element对象上存在events,执行genHandlers第二个参数是false
data += `${genHandlers(el.events, false, state.warn)},`
}
if (el.nativeEvents) {
// AST element对象上存在nativeEvents,执行genHandlers第二个参数是true
data += `${genHandlers(el.nativeEvents, true, state.warn)},`
}
......
return data
}
当遇到AST树中存在events或者nativeEvents属性,会执行genHandlers方法生成字符串代码。
export function genHandlers (
events: ASTElementHandlers,
isNative: boolean,
warn: Function
): string {
// 根据传入的isNative参数拼接结果字符串
let res = isNative ? 'nativeOn:{' : 'on:{'
// 遍历events,拿到每一个事件名
for (const name in events) {
// 执行genHandler方法拼接字符串
res += `"${name}":${genHandler(name, events[name])},`
}
// 截断最后一个大括号,拼接大括号
return res.slice(0, -1) + '}'
}
执行genHandlers之后:
如果是events对象,如拼接非native click事件的code的结果为:
on:{click:${genHandler(name, events[name])},
如果是nativeEvents对象,如拼接native click事件的code的结果为:
nativeOn:{click:${genHandler(name, events[name])},
genHandler
const modifierCode: { [key: string]: string } = {
stop: '$event.stopPropagation();',
prevent: '$event.preventDefault();',
self: genGuard(`$event.target !== $event.currentTarget`),
ctrl: genGuard(`!$event.ctrlKey`),
shift: genGuard(`!$event.shiftKey`),
alt: genGuard(`!$event.altKey`),
meta: genGuard(`!$event.metaKey`),
left: genGuard(`'button' in $event && $event.button !== 0`),
middle: genGuard(`'button' in $event && $event.button !== 1`),
right: genGuard(`'button' in $event && $event.button !== 2`)
}
function genHandler (
name: string,
handler: ASTElementHandler | Array<ASTElementHandler>
): string {
if (!handler) {
return 'function(){}'
}
// 之前生成的handler如果是数组,遍历数组执行genHandler再拼接
if (Array.isArray(handler)) {
return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`
}
// 匹配代码路径, 匹配不上情况,例 handleClick($event)
const isMethodPath = simplePathRE.test(handler.value) // 匹配函数名路径,例 handleClick
const isFunctionExpression = fnExpRE.test(handler.value) // 匹配函数路径,例 function(){}
if (!handler.modifiers) {
// 没有其他修饰符,进入该逻辑
if (isMethodPath || isFunctionExpression) {
// 匹配到了路径,直接返回value值
return handler.value
}
/* istanbul ignore if */
if (__WEEX__ && handler.params) {
return genWeexHandler(handler.params, handler.value)
}
// 没匹配到路径,返回下面这种格式 如我们定义的value是handleClick($event),
// 返回`function($event){handleClick($event)}`
return `function($event){${handler.value}}` // inline statement
} else {
// 有修饰符进入该逻辑
let code = ''
let genModifierCode = ''
const keys = []
// 遍历修饰符
for (const key in handler.modifiers) {
// 在modifierCode对象中查找,匹配上赋值给genModifierCode
// 例如我们定义了修饰符 prevent
if (modifierCode[key]) {
// prevent生成的代码: genModifierCode = '$event.preventDefault();',
genModifierCode += modifierCode[key]
// left/right
if (keyCodes[key]) {
keys.push(key)
}
} else if (key === 'exact') {
// 没匹配上modifierCode,修饰符是exact,进入该逻辑处理
const modifiers: ASTModifiers = (handler.modifiers: any)
genModifierCode += genGuard(
['ctrl', 'shift', 'alt', 'meta']
.filter(keyModifier => !modifiers[keyModifier])
.map(keyModifier => `$event.${keyModifier}Key`)
.join('||')
)
} else {
// 如果没匹配上以上逻辑,将key push进入keys
keys.push(key)
}
}
// keys有值,执行genKeyFilter,这个函数就是处理一些键盘事件
if (keys.length) {
code += genKeyFilter(keys)
}
// Make sure modifiers like prevent and stop get executed after key filtering
// 经过以上处理后genModifierCode仍存在,将该字符串拼接
// 这里genModifierCode = '$event.preventDefault();',
if (genModifierCode) {
code += genModifierCode
}
// 判断生成handlerCode
const handlerCode = isMethodPath
? `return ${handler.value}($event)`
: isFunctionExpression
? `return (${handler.value})($event)`
: handler.value
/* istanbul ignore if */
if (__WEEX__ && handler.params) {
return genWeexHandler(handler.params, code + handlerCode)
}
// 返回下面的拼接字符串
// `function($event){$event.preventDefault();return handleClick($event)`
return `function($event){${code}${handlerCode}}`
}
}
genHandler是最终的代码生成逻辑,首先会通过正则字符串匹配我们定义的事件的格式,再根据是否存在修饰符进入不同的逻辑:
1.没有修饰符的时候:
- 匹配上函数名:
eg: @click="handleClick"
genCode: `handleClick`
- 匹配上函数:
eg: @click="function() {return ''}"
genCode: `function() {return ''}`
- 没匹配上以上格式
eg: @click="handleClick($event)"
genCode: `function($event){handleClick($event)}`
2.有修饰符的时候
eg: @click.prevent="handleClick"
genCode: ``function($event){$event.preventDefault();handleClick($event)`
小结
event的genCode会根据event或nativeEvent类型生成不同的对象,结合定义的修饰符,执行genHandler函数,根据匹配的事件对象中的value内容,生成不同的代码字符串。
下一节我们会继续分析event的执行过程。