1.patch
通过vue2.x源码解析六——数据驱动,当我们通过 createComponent 创建了组件 VNode,接下来会走到
vm._update —> vm.patch –> patch 方法,
去把 VNode 转换成真正的 DOM 节点。
这个过程我们在前一章已经分析过一个普通的 VNode 节点的path过程,但是针对的是一个普通的 VNode 节点,接下来我们来看看组件的 VNode 会有哪些不一样的地方。
patch 的过程会调用 createElm 创建元素节点实现虚拟DOM映射为真实DOM(要注意区分,render也就是参数虚拟DOM用的是createElement,映射为真实DOM createElm),回顾一下 createElm 的实现,它的定义在 src/core/vdom/patch.js 中:
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// ...
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
// ...
}
2.createComponent
我们删掉多余的代码,只保留关键的逻辑,上面的代码会判断 createComponent(vnode, insertedVnodeQueue, parentElm, refElm) 的返回值,如果为 true 则直接结束。
createComponent其实调用的就是给逐渐VNode添加的init方法
那么接下来看一下 createComponent 方法的实现:
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
// 判断vnode.data中是否有hook,并且有init方法(因为上一节讲了会给组件merage一些钩子,其中就有init,所以这里是true)
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
}
}
}
2.1 对vnode.data 做了一些判断
let i = vnode.data
if (isDef(i)) {
// ...
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
// ...
}
// ..
}
vnode 是一个组件 VNode,那么条件会满足,并且得到 i 就是 init 钩子函数
判断vnode.data中是否有hook,并且有init方法(因为上一节讲了会给组件VNode merage一些钩子,其中就有init,所以这里是true),就会调用init方法
2.2 init方法
init方法定义在 src/core/vdom/create-component.js 中:
其实就是和组件的data.hook钩子合并的 componentVNodeHooks 钩子对象的init方法
const componentVNodeHooks = {
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 {
//child是一个vnode实例
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
//调用 $mount 方法挂载子组件
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
},
insert (vnode: MountedComponentVNode) {
},
destroy (vnode: MountedComponentVNode) {
}
}
init 钩子函数执行也很简单,我们先不考虑 keepAlive 的情况,它是通过 createComponentInstanceForVnode 创建一个 vnode 的实例,然后调用 $mount 方法挂载子组件
2.3 createComponentInstanceForVnode方法
创建一个 vnode 的实例
在src/core/vdom/create-component.js中
export function createComponentInstanceForVnode (
vnode: any, // 组件VNode
parent: any, // 当前vue实例vm
): Component {
// 定义参数
const options: InternalComponentOptions = {
_isComponent: true,
// 父VNode,是一个占位节点,Vue实例A调用B组件,B调用C组件,_parentVnode就是C组件占位符
_parentVnode: vnode,
//表示当前激活的子组件的父级实例,例如 app =new Vue,并调用子组件,parent就是app
parent
}
...
// 由上一节我们知道组件会生成子构造器,vnode.componentOptions.Ctor 对应的就是子组件的构造函数
return new vnode.componentOptions.Ctor(options)
}