childComp 组件
new VueComponent()
- 在进入子组件之前,我们要知道
VueComponent构造函数是什么。
function VueComponent(options) {
this._init(options);
};
这里的 this 指向的是当前 VueComponent 实例,而 VueComponent 是继承自 Vue 的,所以这个 _init 方法就是我们初始化 Vue 的时候看到的方法。
传入的 options 为:
{
_isComponent: true,
_parentVnode: vnode, // `vue-component-1` 组件
parent: activeInstance // 也就是我们存入的当前 vm,作为父节点
}
- 进入
childComp组件的_init()方法,再次赋值vm = this, 注意这个时候的 vm 就改变了,变成 childComp 这个 vueComponent 的实例了 - 组件合并配置会走
initInternalComponent方法,其中将vm.constructor.options也就是Sub的options属性(childComp对象)添加到了vm.$options属性上。 - 然后执行
chilComp组件的生命周期:
parent beforeCreate
parent created
- 最后由于
vm.$options上没有el属性,这是和Vue初始化不同的地方,走到这就跳出了_init方法,也跳出了 VueComponnet 的构造函数,再跳出了 createComponentInstanceForVnode 方法,来到了 vue-component-1 这个 vnode 的 data.hook.init 方法。
var child = (vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance));
child.$mount(hydrating ? vnode.elm : undefined, hydrating); //
也就是我们以上得到的 VueComponent 实例现在复制给了 child,且执行了 child 的 $mounted 方法,这就是 Vue 的 $mounted 方法,只是这时传入的参数变了,el 为 undefined。
Vm.$mounted
- 这里由于
childComp组件没有render,所以需要根据其template去生成render:
// <div>{{msg}}<button-counter/></div>
ƒ anonymous(
) {
with(this){return _c('div',[_v(_s(msg)),_c('button-counter')],1)}
}
- 到
mountedComponent方法之后,设置了vm.$el为el(undefined),然后执行生命周期
parent beforeMount
- 接着又到了
updateComponent方法,调用_render和_update方法。
Vm._render()
- 在
_render方法中,将vm.$vnode赋值为vm.$options._parentVnode,当前为vue-component-1这个vnode。 - 设置
currentInstance=vm,设置成了当前childComp的VueComponent实例。 - 根据生成的
render函数,我们会依次执行:_c('button-counter'),_s(msg),_v(_s(msg),_c('div',[_v(_s(msg)),_c('button-counter')],1)。也就是先生成两个vnode作为children再去生成外层的vnode。
-
_s(msg)_s = toString得到字符串:QAAA -
_v(_s(msg)_v = createTextVNode得到vnode:
{tag: undefined, text: 'QAAA', children: undefined, parent: undefined,...}
_c('button-counter')在render过程的_createElement方法中,用户手写的render传入的tag会是一个对象,通过el或者template生成的tag一般都是一个字符串。当前_c('button-counter')传入的tag是button-counter,这不是一个保留的页面标签,所以会执行:
isDef((Ctor = resolveAsset(context.$options, 'components', tag))))
vnode = createComponent(Ctor, data, context, children, tag);
通过 resolveAsset 得到的 Ctor ,其实就是 components 属性中注册的 ButtonCounter 组件,然后将这个组件对象传到 createComponent 方法。
经过 _render 会生成 tag 为 vue-component-2-button-counter 的 vnode,由于是组件的 vnode ,所以有 date.hook.init 方法且没有 children。
_c('div',[_v(_s(msg)),_c('button-counter')],1)
这样将子节点都转换成 vnode 后,然后再执行外层的 _c('div',[_v(_s(msg)),_c('button-counter')],1),由于此时传入的 tag 是 div 这个保留标签,所以直接通过 new Vnode() 生成的 vnode。
到这就会继续回到 childComp 的 _render() 方法中继续执行,给 vnode 添加 parent 属性,最终生成的 vnode 为:
tag: 'div',
context: {msg: 'QAAA', $vnode: {tag: 'vue-component-1', ...}},
chidren: [
{tag: undefined, text: 'QAAA',... },
{
tag: 'vue-component-2-button-counter',
data:{on: undefined, hook: {…}},
children: undefined,
parent: undefined,
componentOptions: {propsData: undefined, listeners: undefined, tag: 'button-counter', children: undefined, Ctor: ƒ}
... }
],
parent: {tag: 'vue-component-1', data: {…}, children: undefined, …},
componentOptions: undefined,
...
Vm._update()
-
在
_update方法中,将preEl = vm.$el,prevVnode = vm._vnode,当前的vm.$el和vm._vnode都为null,vm._vnode在initRender的时候就直接赋为null了。 -
将当前
vm(div VueComponent) 设置为了全局变量activeInstance,且将vm保存在了局部作用域中的prevActiveInstance变量中,在执行完__patch__方法后会将保存的vm重新设置回activeInstance变量中。 -
将
vm._vnode设置为了刚生成的vnode(div Vnode) -
然后执行
__patch__方法,传入了vm.$el(undefined) 和生成的vnode
接下来会执行 childComp 组件的 _update,这一次传入 patch 方法的 oldVnode 参数为 vm.$el,其值为 undefined。
会走到 createElm(vnode, insertedVnodeQueue),但是再次进入 UcreateComponent 方法后 vnode.data 属性此时为 undefined,因为现在的 vnode 是 div 元素,不再是一个组件,会跳过这个方法继续向下执行。
vnode.elm = nodeOps.createElement(tag, vnode);
首先会调用 nodeOps 模块的 createElement 方法,根据 tag 和 vnode 生成一个真实 Dom 即一个空的 div 标签:<div></div>
与上个很大不同在,此时的 vnode 的 children 有两个 vnode 子节点,会进入 createChildren 方法。
在
createElm方法中, 组件vnode会走createComponent方法。
但是非组件vnode不会,如果当前vnode节点的tag是保留标签,它有children所以会走createChildren方法,先创建子节点再进行插入。如果是注释或者文本节点,直接根据父节点和兄弟节点进行插入。
createChildren方法会遍历children,对每个vnode子元素执行createElm方法。
- 第一个子节点就是一个文本节点的
vnode,所以会走到:
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
将文本元素插入到 parentElm 这个空的 div 中,此时的 vnode.elm 为 <div>QAAA</div>。
- 第二个子节点是一个组件,所以会走到
UcreateComponent方法,执行button-counter组件的初始化。