指令是 模板解析
的续章,本文会尝试从源码的角度来理解 指令
是如何被提取和应用的。
指令的提取
指令的提取过程是在parse阶段进行的,在 parseHTML
方法中,会解析字符串模板为如下的单个ast对象
<div class="app">
hello {{ a }}
<span v-for='i in 5'>{{i}}</span>
</div>
被解析为如下文本
{
"type": 1,
"tag": "div",
"attrsList": [
{
"name": "class",
"value": "app"
}
],
"attrsMap": {
"class": "app"
},
"children": []
}
其中的 <span v-for='i in 5'>{{i}}</span> 被解析为如下文本
{
"type": 1,
"tag": "span",
"attrsList": [
{
"name": "v-for",
"value": "i in 5"
}
],
"attrsMap": {
"v-for": "i in 5"
},
"parent": {
"type": 1,
"tag": "div",
"attrsList": [],
"attrsMap": {
"class": "app"
},
"children": [
{
"type": 2,
"expression": ""\n hello "+_s(a)+"\n "",
"tokens": [
"
hello ",
{
"@binding": "a"
},
"
"
],
"text": "
hello {{ a }}
"
}
],
"plain": false,
"staticClass": ""app""
},
"children": []
}
通过提取为格式化的对象,就可以对单个的节点进行解析了
指令的解析
首先对 v-for
, v-if
, v-once
三个指令进行解析
// processFor 调用 parseFor,
// 通过 /([^]*?)s+(?:in|of)s+([^]*)/ 正则表达式match后,
// 将 v-for='i in 5' 解析为 {for: "5", alias: "i"} 对象
// 再把 {for: "5", alias: "i"} 与 element 对象合并
processFor(element)
processIf(element) // 同上
processOnce(element) // 同上
// processElement 是一个比较大的方法,
// 里面对 key ref slot component attrs进行了提取和解析
// 其中的 processAttrs 方法,会提取attrs里面的 v-bind(:) v-on(@)
// 构造成props, attrsMap, events, directives等对象
// 然后与 element 对象合并
// 值得一提的是 v-model 会被当做props,然后再次进入directives中处理
processElement(element)
指令的处理
通过上面的步骤,Vue 提取出了 props
, events
, attrsMap
, for
等指令对象,那么 Vue 又是如何去处理这些指令的呢? codegen/index.js
中有这样一些代码:
...
if (el.key) {
data += `key:${el.key},`
}
// ref
if (el.ref) {
data += `ref:${el.ref},`
}
...
if (el.attrs) {
data += `attrs:{${genProps(el.attrs)}},`
}
if (el.props) {
data += `domProps:{${genProps(el.props)}},`
}
if (el.events) {
data += `${genHandlers(el.events, false, state.warn)},`
}
...
上面这些代码会对每个属性进行处理,然后拼接为字符串
比如这段代码
<div class="app">
hello {{ a }}
<span v-for='i in 5' @click='a'>{{i}}</span>
<input type="text" v-model='a'>
</div>
会被处理为
_c('div',{staticClass:"app"},[_v("
hello "+_s(a)+"
"),_l((5),function(i){return _c('span',{on:{"click":a}},[_v(_s(i))])}),_v(" "),_c('input',{directives:[{name:"model",rawName:"v-model",value:(a),expression:"a"}],attrs:{"type":"text"},domProps:{"value":(a)},on:{"input":function($event){if($event.target.composing)return;a=$event.target.value}}})],2)
这段代码在 to-function.js 中,会被 createFunction 处理为一个匿名函数
剩下的事情,就交给 vm.$createElement
去生成vNode了。