学习vue源码,添加注释。
-
把v-for="(value, key, index) in object"解析为语法树上的属性
el = { for: "object", alias: "value", iterator1: "key", iterator2: "index" } -
根据ast语法树结果 生成对应的render字符串
"..._l((object),function(value,key,index){return _c('p',[_v(_s(index)+". "+_s(key)+" : "+_s(value))])}))" -
执行render字符串
_l
返回一个VNode数组
-
把v-if="exp1" v-else-if="exp2" v-else分别解析为ast上的属性
el1 = { ... if: exp1, ifConditions: [{ // v-if exp: 'exp1', block: el1 // v-if }, { exp: 'exp2', block: el2 // v-else-if }, { exp: undefined, block: el3 // v-else }], ... } el2 = { ... elseif: 'exp2' ... } el3 = { ... else: true ... }
-
生成render字符串 '(value == 1)?_c('p',[_v("v-if块的内容")]):(value == 2)?_c('p',[_v("v-else-if块的内容")]):_c('p',[_v("v-else块的内容")])'
-
注意这三个在ast中会和成一个, 添加到parent的children中.
- v-once其实更像一个标识作用,以在其他操作的时候进行特殊处理
- ast阶段会把once指令解析为ast上的once:true属性
- 在genOnce中大概有三种情况
- if 如果once与if同用,则优先处理if指令,if指令中再调用genOnce就走到第三种情况了
- for 在for上使用的时候,重点关注是否使用了key属性;为添加key则按普通处理,添加了key则用_o包裹生成的内容;
- 普通 和静态节点一样处理 genStatic
- vnode阶段
v-once只解析一次,此后按静态元素处理;
_o执行的时候其实就是给vnode上添加了key属性,再执行个node.isStatic = true node.isOnce = true
- v-show指令主要作用在vnode上
- 其根据
value的值
来控制元素的display属性;如果有过渡效果
则调用entry,leave添加过渡效果。
<!-- value = '祖国统一' -->
<span v-text="value"></span>
- ast
会把v-text处理为ast上的el.directives数组中的一项
然后经过prop处理会添加到el.props中的一项值会被修改为"_s(value)"
el = {
....
attrsList: [{
end: 43,
name: "v-text",
start: 29,
value: "value"
}],
attrsMap: {
v-text: "value"
},
directives: [{
arg: null,
end: 43,
isDynamicArg: false,
modifiers: undefined,
name: "text",
rawName: "v-text",
start: 29,
value: "value"
}],
rawAttrsMap: {
v-text: {name: "v-text", value: "value", start: 29, end: 43}
},
props: [{
dynamic: undefined,
end: 43,
name: "textContent",
start: 29,
value: "_s(value)"
}]
....
}
- render字符串
通过genData被转化为"{domProps:{"textContent":_s(value)}}" - vnode
// VNode
{
....
data: {
domProps: {
textContent: "祖国统一"
}
}
....
}
- updateDOMProps
和v-texet类似,把text变为html,textContent变为innerHTML大概就是v-html的处理过程了
- ast 还是会解析为各种ast上的属性的, 但是不做处理
- 重新render出来的DOM不再有此属性
配合
[v-cloak] { display: none }
, 在页面展示前把带有v-cloak
的元素隐藏
- ast
当解析到v-pre指令的时候会在ast上添加pre: true; 同时会设置inVPre为true, inVPre为true时, 会限制对文本的解析;
当处理当前元素end时, 会把inVPre设为false, 结束限制
- number -> _n(value)
- lazy -> 使用input事件替代change事件
- trim
parse -> genDirectives -> gen(model) -> genCheckboxModel/... -> addProp -> addHandler -> genProps --> updateDOMProps
-
select
- ast
和其他的指令处理没啥区别; 多了一个v-for判断, v-model不能与v-for一起使用 - render
会给该select添加一个change事件, render阶段主要是根据v-model的值和修饰符拼一个事件回调函数;// v-model="value.a" var $$selectedVal = Array.prototype.filter.call($event.target.options, function(o){ return o.selected }).map(function(o){ var val = "_value" in o ? o._value : o.value; return val }); $set(value, "a", $event.target.multiple ? $$selectedVal : $$selectedVal[0])
- DOM
inserted上会根据value setSelected更新界面DOM的值.
componentUpdated上也会setSelected更新界面DOM的值; 如果option是用v-for添加的, 也会在这里更新.
- ast
-
checkbox
- ast
主要是关注处理后的props
还有添加到input上的事件回调
props: [{ checked: "Array.isArray(value)?_i(value,null)>-1:_q(value,"yes")" }]
var $$a = value, $$el = $event.target, $$c = $$el.checked ? (trueValueBinding) : (falseValueBinding); if(Array.isArray($$a)){ var $$v = number ? '_n(' + valueBinding + ')' : valueBinding, $$i = _i($$a,$$v); if ($$c) { ($$i < 0 && value = $$a.concat($$v)) } else { $$i > -1 && value = $$a.slice(0,$$i).concat($$a.slice($$i+1))) } } else { value = $$c }
- ast
主要是关注处理后的props
-
radio
- ast
和其他的差不多
props: [{ name: "checked", value: "_q(value,"1")" }] // 事件回调 value = "1"
- render domProps:{"checked":_q(value,"1")}
- ast
和其他的差不多
-
其他类型的input textarea 主要处理逻辑:
- 根据lazy判断需要绑定的事件类型, lazy绑定change事件, 否则绑定input事件
- 处理trim
- 添加对输入法模式的处理
- ast上添加props属性
props: [{ name: "value", vlaue: "(value)" }]
- 添加事件处理
events: [{ input: { dynamic: undefined, value: "if($event.target.composing)return;value=$event.target.value" }, blur: { value: "$forceUpdate()" } }]
- trim和number的情况, 还会添加blur事件
- updateDOMProps
-
v-model用到自定义组件上
<!-- value: "哈哈" --> <my-component v-model="value"></my-component>
- ast
把自定义组件上的v-model属性解析为el.model// v-model="value" el.model = { callback: "function ($$v) {value=$$v}", expression: ""value"", value: "(value)" }
- render
"_c('my-component',{model:{value:(value),callback:function ($$v) {value=$$v},expression:"value"}})" - _c createComponent
生成组件阶段会把model进行转换, 最终会变为ast上的componentOptions的属性自定义组件的model event属性用来设置事件类型 默认为input prop属性用来设置v-model的值以何标识符传入自定义组件中componentOptions: { listeners: {input: function ($$v) {value=$$v}}, propsData: {value: "哈哈"} }
- ast
- 父组件
<div slot="xxx">xxx</div>
- html -> ast
会把slot="xxx"解析为el.slotTarget;
并且会把slot添加到el.attrs上; - ast -> render
处理为"_c('div', {attrs:{"slot":"header"},slot:"header"})" - render -> VNode
node.data = { attrs: { slot: "header" }, slot: "header" }
- html -> ast
- 自定义组件
- 自定义组件init
初始化自定义组件的过程, 会把自定义组件内的子元素带过来, 分组处理为vm.$slotvm.$slots = { default: [VNode, VNode....], // 为命名的内容用会当道default上 xxx: [VNode] .... }
<slot name="xxx"></slot>
- html -> ast (处理slot元素) el.slotName = "xxx"
- ast -> render "_t("xxx")"
- _t
根据slot上的name xxx, 获取自定义组件内对应子元素的VNode
- 自定义组件init
- 父组件
<app-out> <template slot="item" slot-scope="aaa"> <li>{{ aaa.text }}{{ aaa.name }}</li> </template> </app-out>
- html -> ast
el = { tag: "tempalte", slotTarget: ""item"", slotScope: "aaa" }
- 对template元素的特殊处理
会把当前template的ast添加到其父节点的scopedSlots属性上然后会把整个template的ast从ast树中过滤掉el:parent = { scopedSlots: { ""item"": el:template } }
- el:parent ast -> render
// render字符串对应的函数 // 这里因为aaa是来自子组件的 所以以函数的方式放到scopeSlots中, 到自定义组件初始化的时候再执行 _c('app-out', { // scopeSlots数组是自定义组件中包含的作用域插槽 scopedSlots:_u([{ // _u把数组处理为键值对形式 key: "item", // fn的返回值是对应作用域插槽中的children对应的VNode fn: function(aaa){ return [_c('li',[_v(_s(aaa.text)+_s(aaa.name))])] } }]) } )
- html -> ast
- 子组件
<ul> <slot name="item" v-for="item in items" :text="item.text" v-bind="{name: '123'}"> </slot> </ul>
- 子组件render之前, 会从parentVnode中取到scopeSlots处理后赋值给vm.$scopeSlots
- html -> ast 和slot中对slot标签的处理方式一样
- ast -> render
// render字符串对应的函数 _c('ul', [_l(items, function(item) { return _t('item', null, { 'text': item.text }, { name: '123' }); })]);
- render -> VNode
主要是_t的处理.
根据_t的name, 执行对应的fn, 传入参数,返回对应的VNode
- keep-alive本身是一个抽象组件, 不渲染为DOM, 组件内会对内部的组件cache
- keep-alive组件的render返回其内的第一个自定义组件的vnode
- 经过keep-alive的组件, 其vnode上会添加vnode.data.keepAlive = true