2.4.1节设计的 v-on 语法仅接受方法名: v-on:dblclick="editTodo"
,由于语法过于局限,所以没法在触发 editTodo 事件的时候知道当前元素映射的数据,更好的语法应该是 v-on:dblclick="editTodo(todo)"
,此外这一节也新增点语法糖: Vue 的事件修饰符。
我们先来完善一下 v-on 的语法表达。回顾上一节 v-on 语法产生的 render code :
_c('button', {
on: { "click": clickme }
}, [ _v("click me")] )
如果案例改成 v-on:dblclick="editTodo(todo)"
的话,那么 render code 就变成了:
_c('button', {
on: { "dblclick": editTodo(todo) }
}, [ _v("click me")] )
等同于最后:
buttonDom.addEventListener('dblclick', editTodo(todo))
这就要求 editTodo 函数返回一个函数才行,但是这个显然不符合我们的预期,我们所期望的 render code 应该是:
_c('button', {
on: { "dblclick": function($event) { editTodo(todo) } }
}, [ _v("click me")] )
通过一个匿名的函数包裹就可以完成,所以我们稍微改造一下 codegen/events.js 里边的 genHandler 实现:
// compiler/codegen/events.js
// v-on:click="function(){}"
// v-on:click="() => {}"
const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/
// v-on:click="xxx" // xxx为vm的一个方法名字
const simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/
function genHandler (name, handler) {
if (!handler) {
return 'function(){}'
} else if (Array.isArray(handler)) {
return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`
} else {
//支持:v-on:click="removeTodo(todo)" 和 v-on:click="xx"
return fnExpRE.test(handler.value) || simplePathRE.test(handler.value)
? handler.value
: `function($event){${handler.value}}`
}
}
只要 v-on 的属性值是一个函数调用,我们就把它包裹在一个匿名 function 里边,提供 $event 的参数。
现在 todo 的案例就基本可以跑起来了。
为了让代码更加精简,Vue 还提供了 v-on 的简写模式以及修饰符 (官方文档),列举几个官方的案例:
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 只有在 keyCode 是 13 时调用 vm.submit() -->
<input v-on:keyup.13="submit">
<!-- 缩写语法 -->
<input @keyup.enter="submit">
v-on:click 的简写模式是 @click,这部分仅涉及到一个正则的小改动,所以就不在这展开篇幅了,要新增语法糖依旧是之前的四个步骤,我们就不展开这4个步骤,我们重点看看 事件修饰符 和 键值修饰符 的设计思路。
前边的案例展开成 render code:
_c('button', {
on: { "dblclick": function($event) { editTodo(todo) } }
}, [ _v("click me")] )
前边也提到,事件的处理被包裹在一个匿名闭包里边,真正添加到 Dom 元素的 handler 实际是这个匿名函数。这就可以带给我们一个思路,我们只要在这个匿名函数前边加点逻辑就可以达到事件修饰符和键值修饰符的目的了:
_c('button', {
on: { "dblclick": function($event) {
$event.stopPropagation();
if ($event.keyCode !== 13) return null;
editTodo(todo)
} }
}, [ _v("click me")] )
只有当 $event 符合我们修饰符所描述的规则,最后才会触发到真正的 editTodo(todo) 调用。
基于这个思路,继续改造上边提到的 codegen/events.js 里边的 genHandler 实现:
// keyCode aliases
const keyCodes = {
esc: 27,
tab: 9,
enter: 13,
space: 32,
up: 38,
left: 37,
right: 39,
down: 40,
'delete': [8, 46]
}
const genGuard = condition => `if(${condition})return null;`
// 生成的代码就变成
/*
function ($event) {
if ($event.target !== $event.currentTarget) return null;
if ($event.keyCode !== 13) return null;
}
*/
const modifierCode = {
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(`$event.button !== 0`),
middle: genGuard(`$event.button !== 1`),
right: genGuard(`$event.button !== 2`)
}
function genHandler (name, handler) {
if (!handler) {
return 'function(){}'
} else if (Array.isArray(handler)) {
return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`
} else if (!handler.modifiers) { // 没有修饰符的话 .stop .prevent .self
//支持:v-on:click="removeTodo(todo)" 和 v-on:click="xx"
return fnExpRE.test(handler.value) || simplePathRE.test(handler.value)
? handler.value
: `function($event){${handler.value}}`
} else {
let code = ''
const keys = []
for (const key in handler.modifiers) {
if (modifierCode[key]) {
code += modifierCode[key]
} else {
keys.push(key)
}
}
/* genKeyFilter(keys) 生成前缀判断条件:
{
if ($event.keyCode !== 13 && _k($event.keyCode,"enter", 13)) return null;
}
*/
if (keys.length) {
code = genKeyFilter(keys) + code
}
const handlerCode = simplePathRE.test(handler.value)
? handler.value + '($event)' // v-on:click="xxx" // 生成 xxx($event)
: handler.value // v-on:click="console.log(xxx);xxxx;"
return `function($event){${code}${handlerCode}}`
}
}