一.组件渲染
从前面的文章分析可以知道Vue.prototype._render
函数生成的vnode
有两种类型,这是根据传入的options.tag
来判断。
- 普通
vnode
: 也就是typeof tag===string
- 组件
vnode
:options
中的tag
为对象
在_update
方法中传入生成的vnode
,进而调用__patch__
方法,在__patch__
方法中,又会调用createElm
方法将vnode
对应的真实dom
也就是vnode.elm
插入到父节点parentElm
上。
在
createElm
方法进行插入的过程中会首先调用createComponent
方法,组件vnode
执行该方法,并返回true
,不执行后续逻辑。
二.实例分析
// index.js
import SubComp from '@components/SubComp.vue'
new Vue({
el: '#app',
render(h){
return h(SubComp)
}
})
// subComp.vue
<template>
<div>
{{text}}
</div>
</template>
<script >
export default {
name: 'subComp',
data() {
return {
text: 'subComp'
}
}
}
</script>
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
</div>
</body>
<script src="./dist/app.bundle.js"></script>
</html>
SubComp
会被vue-loader
编译成tag
为一个对象,所以第一次调用render
方法生成的vnode
为一个组件vnode
.
在当前new Vue( )
生成的实例的_update
方法中会将生成的组件vnode
作为参数进行调用,执行__patch__
方法,__patch__
方法中又会执行createElm
方法,这和普通vnode
的渲染逻辑一样.不同的是createElm
方法。
在createElm
方法中会首先尝试执行createComponent(vnode)
,如果结果为false,就继续执行当前方法createElm
中的后续逻辑,但我们的实例中返回的结果为true
,所以后续的方法不会执行。
下面我们来看看createComponent
方法
三. createComponent
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
记住一点,此时的vnode
还是首次调用render
方法生成的vnode
,createComponent
方法主要做了以下几件事
- 根据组件
vnode
生成组件实例 - 在
initComponent
方法中进行赋值,vnode.elm = vnode.componentInstance.$el
这里的$el
,是组件实例渲染而成的真实dom
- 插入
insert(parentElm, vnode.elm, refElm)
,这里的parentElm
也就是body
经过上面的三步,组件vnode
被渲染成真实的dom
并插入到body
中,但在此之前,vue
还做了很多其他的工作。
我们知道在生成组件vnode
时会添加相应的钩子函数,在src/core/vdom/create-component.js
的createComponent
方法中执行了installComponentHooks
方法,这个方法是给组件vnode
的data
中添加相应的钩子函数,因此上面代码中的data.hook.init
也就是下面的方法
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
在init
方法中会调用createComponentInstanceForVnode
方法,会根据vnode
生成组件实例(前面的文章已经分析如何生成组件实例,这里不作赘述),并赋值给当前vnode
的componentInstance
属性.然后手动调用$mount
方法
注意这里的
vnode
还是首次_render
函数生成的组件vnode
调用$mount
方法又会进入组件实例(继承自Vue
)的mountComponent方法
,然后调用组件实例child
的__update
,然后__patch__
,然后createElm
,然后insert
,在组件实例对应的insert
方法执行完成后,组件SubComp
实例的$el
属性是真实的dom
,然后赋值给vnode.elm
,也就是vnode.elm = vnode.componentInstance.$el
,最后执行insert(parentElm, vnode.elm, refElm)
。
经过上面的分析,我们需要了解以下两点
- 每个组件都会生成一个组件实例,组件实例具有
Vue
的所有属性和方法,因此在手动执行$mount
方法后,会执行mountComponent
方法,执行_render
,执行_update
,执行__patch__
,执行createElm
,执行insert
方法,一系列方法执行后,最终完成了子组件的vnode
渲染成真实的dom
($el
)过程。子组件SubComp
的流程跑完后会继续执行后续逻辑insert(parentElm, vnode.elm, refElm)
。 - 无论时普通
vnode
还是组件vnode
都是先子后父插入,当前节点的子节点插入到父节点完成后,才会继续当前节点的插入,以确保节点的父子关系。
查看demo代码,请根据当前文章对demo源码中的index.js
或者webpack.config.js
作相应改动.