main.js
new Vue({
el:"#app",
name:"container",
render:h=>h(App3)
})
App3是文件的入口
<template>
<div>
<div>{{name}}</div>
<App2></App2>
</div>
</template>
子组件App2中有孙组件App1
<template>
<div>
<div>{{name}}</div>
<App1></App1>
</div>
</template>
App1.vue
<template>
<div>
<div>{{name}}</div>
</div>
</template>
主要流程如下(过程省略Vue的初始化)
main.js中的render会直接调用的就是_createElement,因为APP是个对象,所以会走以下逻辑
function _createElement(context,tag,data,children,normalizationType){ else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag)))
{
vnode = createComponent(Ctor, data, context, children, tag);} //创建组件
}
function createComponent (Ctor, data, context, children, tag){
//省略无关代码,主要执行了以下两个方法
installComponentHooks(data);// return a placeholder vnodevar //注册四个钩子函数
name = Ctor.options.name || tag;var
vnode = new VNode( //创建componentVnode,该vnode会接下来执行_update函数
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory);
}
上面返回的组件函数会作为Vue.prototype._update函数的参数,在_update函数中主要执行了patch方法,patch函数在初次渲染时会调用createElm函数
function createElm(vnode, insertedVnodeQueue, parentElm, refElm){
//r如果vnode不是组件vnode的话,这个函数返回false,函数继续执行,创建真实dom
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return }
//如果不是组件函数,创建真实dom
var data = vnode.data;
var children = vnode.children;
var tag = vnode.tag;
if (isDef(tag)) {
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode);
setScope(vnode);
/* istanbul ignore if */
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text);
insert(parentElm, vnode.elm, refElm);
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
//如果不是组件vnode的话,里面的i.hook为undefined,因为这些钩子函数是在_createElement里的createComponent函数注册的
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */); //这里箱相当于执行了this.init(vnode)
}
var componentVNodeHooks = {
init: function init (vnode, hydrating) {
// 实现判断是否keepAlive
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
// 首次渲染执行createComponentInstanceForVnode
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
destroy:...
prepatch:..
insert:...}
function createComponentInstanceForVnode (vnode,parent) {
var options = {
_isComponent: true,
_parentVnode: vnode,
parent: parent
};
//new Vnode的时候定义了this.componentOptions = componentOptions,就是一个VueComponent构造函数
return new vnode.componentOptions.Ctor(options) //这里会new一个新的vm,uid++,options里的_isComponent为true
}
Vue.prototype._init = function (options) {
var vm = this;
vm._uid = uid$3++;
// a flag to avoid this being observed
vm._isVue = true;
// merge options
if (options && options._isComponent) { //走组件初始化init的时候,_isComponent为true
initInternalComponent(vm, options); // 新实例的options设置
}
...}
// 新实例构建完成后,继续执行
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
子实例mount的时候也会走vm._update(vm._render())
在render函数里
Vue.prototype._render = function () {
vm.$vnode = _parentVnode; //这个就是父级的createcomponent的产物,tag为组件名称
// render self
var vnode;
try {
currentRenderingInstance = vm;
vnode = render.call(vm._renderProxy, vm.$createElement);
}
.....
return vnode //返回的vnode作为参数传入update函数
}
Vue.prototype._update = function (vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
var restoreActiveInstance = setActiveInstance(vm);
vm._vnode = vnode; //这里给vm的_vnode赋值
//也就是说 _vnode = render.call(vm._renderProxy, vm.$createElement);
.....
}
这里的render函数是由模板字符串编译成ASTs,再有AST转换为render函数字符串
App3.vue的template
会转换成
在vue/src/core/instance/render-helpers/index.js定义了这些和渲染vnode相关的函数
_c: createElenent //这个比较特殊,是在initRender函数里面定义的
其余的
target._v = createTextVNode
target._s = toString
这时候的_vnode如下图所示,tag必须是html元素,完全放映了模板的结构
在执行_c("App2"),_c("App1")的时候,其实是执行了从文章主要流程开始的所有的逻辑,并将vnode返回作为父级_c方法的参数children,以此建立父子孙的vnode关系,一颗vnode tree就生成了(顺带说一句,除了最外层new Vue(...options)的根实例,每个组件都有自己对应的实例)
总结组件渲染的流程如下(只总结组件创建相关的流程)