由于2.x版本中引入虚拟DOM的缘故,Vue支持模板和手写render两种方式(也支持jsx,属于第三方插件帮你转换为render函数;当然模板方式最终也是转换为render函数,但属于官方内置编译器),官方推荐模板这种写法,降低使用门槛。
Vue是如何解析模板并挂载到真实的DOM上呢?
先看下Vue API层是如何使用的:
// 使用Vue.prototype.$mount(el)或者放在options.el里
new Vue({template, data}).$mount(el)
// or
new Vue({ el, template, data})
先了解下源码入口区别:
我们源码分析的是带编译器的运行时,以下看下笔者简化的源码流程,注意高亮的关键代码:
const { render } = compileToFunctions(options.template, ...)
createCompiler
(baseOptions) 'src/platforms/web/compiler/index.js'
baseCompile(template, options)
) 'src/compiler/index.js'
ast = parse(template.trim(), options)
解析模板字符串生成 AST 'src/compiler/parser/index.js'
// AST数据结构
// 最终生成的是一颗AST树结构
declare type ASTElement = {
type: 1;
tag: string;
attrsList: Array<{ name: string; value: any }>;
attrsMap: { [key: string]: any };
parent: ASTElement | void;
children: Array<ASTNode>; // ASTNode = ASTElement | ASTText | ASTExpression;
processed?: true;
static?: boolean;
staticRoot?: boolean;
staticInFor?: boolean;
staticProcessed?: boolean;
hasBindings?: boolean;
text?: string;
attrs?: Array<{ name: string; value: any }>;
props?: Array<{ name: string; value: string }>;
plain?: boolean;
pre?: true;
ns?: string;
component?: string;
inlineTemplate?: true;
transitionMode?: string | null;
slotName?: ?string;
slotTarget?: ?string;
slotScope?: ?string;
scopedSlots?: { [name: string]: ASTElement };
ref?: string;
refInFor?: boolean;
if?: string;
ifProcessed?: boolean;
elseif?: string;
else?: true;
ifConditions?: ASTIfConditions;
for?: string;
forProcessed?: boolean;
key?: string;
alias?: string;
iterator1?: string;
iterator2?: string;
staticClass?: string;
classBinding?: string;
staticStyle?: string;
styleBinding?: string;
events?: ASTElementHandlers;
nativeEvents?: ASTElementHandlers;
transition?: string | true;
transitionOnAppear?: boolean;
model?: {
value: string;
callback: string;
expression: string;
};
directives?: Array<ASTDirective>;
forbidden?: true;
once?: true;
onceProcessed?: boolean;
wrapData?: (code: string) => string;
wrapListeners?: (code: string) => string;
// 2.4 ssr optimization
ssrOptimizability?: number;
// weex specific
appendAsTree?: boolean;
};
optimize(ast, options)
优化语法树 'src/compiler/optimizer.js'
code = generate(ast, options)
生成代码(code对象包含render函数) 'src/compiler/codegen/index.js'
return (isShow) ?
_c('ul', {
staticClass: "list",
class: bindCls
},
_l((data), function(item, index) {
return _c('li', {
on: {
"click": function($event) {
clickItem(index)
}
}
},
[_v(_s(item) + ":" + _s(index))])
})
) : _e()
mount.call(this, el)
Vue.prototype.$mount
'platforms/web/runtime/index.js'
mountComponent
'core/instance/lifecycle.js'
updateComponent
)
vm._render()
'src/core/instance/render.js'
const { render } = vm.$options
return vnode = render.call(vm._renderProxy, vm.$createElement)
vm._update(VNode)
'src/core/instance/lifecycle.js'
vm.__patch__(preVNode, VNode)
'src/platforms/web/runtime/patch.js'
createPatchFunction
--> patch(oldVNode, vnode)
'src/core/vdom/patch.js',详见Virtual DOM实现 const prevVnode = vm._vnode
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
以下是vue挂载的整个流程,其实就是vnode生成真实dom的规则。其中还包括vue自定义组件的构建流程,详细说明可看Vue2.x源码分析 - 组件系统章节。