createElm
函数的作用之前已经讲过了,就是将VNode
转为真实的DOM。
// createElm
// src/core/vdom/patch.js
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// ...
// 【分析createComponent】这里的createComponent跟上一节的createComponent不是同一个函数
// 传给 createComponent 函数的 vnode 参数是组件 VNode,因此 createComponent 函数返回 true ,不会再往下执行
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
}
// createComponent
// src/core/vdom/patch.js
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data;
if (isDef(i)) {
// 和 Keep-alive 相关
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
// 在将组件转为VNode时有这么一行代码, const toMerge = componentVNodeHooks[key];
// 在componentVNodeHooks中定义了init、prepatch、insert、destroy4个钩子函数 【分析init】
// 判断 vnode.data.hook.init 是否存在,这里vnode 是一个组件 VNode,那么条件满足,并且得到 i 就是 init 钩子函数。
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;
}
}
}
// init
// src/core/vdom/create-component.js
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// Keep-alive 相关
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// activeInstance 是一个全局变量,
// 在调用 __patch__ 前先用 prevActiveInstance 保存 activeInstance ,
// 然后将当前实例 vm 赋给 activeInstance ,在执行完 __patch__ 后再恢复 activeInstance 原来的值
// 分析createComponentInstanceForVnode
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// $mount 相当于执行 child.$mount(undefined, false)
// 它最终会调用 mountComponent 方法,进而执行 vm._render() 方法
// 分析render函数
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
}
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode,
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render
options.staticRenderFns = inlineTemplate.staticRenderFns
}
// 这里的 vnode.componentOptions.Ctor 对应的就是子组件的构造函数
// 它实际上是继承于 Vue 的一个构造器 Sub,相当于 new Sub(options)
// 于是又执行了Vue的_init方法
return new vnode.componentOptions.Ctor(options)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// merge options
if (options && options._isComponent) {
// _isComponent是true
// 分析initInternalComponent
initInternalComponent(vm, options)
} else {
// ...
}
// 由于组件初始化的时候是不传 el 的,所以跳过,回到上上个文件
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// 主要记住下面三行代码,回到上一个文件
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
// render
Vue.prototype._render = function (): VNode {
const vm: Component = this
const {
render, _parentVnode } = vm.$options
// const parentVnode = options._parentVnode
// opts.parent = options.parent
// opts._parentVnode = parentVnode
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
// 当前组件的父 VNode
vm.$vnode = _parentVnode
// render self
let vnode
try {
// 当前组件的渲染 vnode
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
// ...
}
// vnode 的 parent 指向了 _parentVnode,也就是 vm.$vnode,它们是一种父子的关系
vnode.parent = _parentVnode
// 我们知道在执行完 vm._render 生成 VNode 后,接下来就要执行 vm._update 去渲染 VNode 了
// 分析vm._update
return vnode
}
// src/core/instance/lifecycle.js
// vm._update
export let activeInstance: any = null
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevVnode = vm._vnode
// 这个 activeInstance 作用就是保持当前上下文的 Vue 实例
// 在实例化子组件的过程中,它需要知道当前上下文的 Vue 实例是什么,并把它作为子组件的父 Vue 实例
// 之前我们提到过对子组件的实例化过程先会调用 initInternalComponent(vm, options) 合并 options,
// 把 parent 存储在 vm.$options 中,在 $mount 之前会调用 initLifecycle(vm) 方法:
// 分析initLifecycle
const prevActiveInstance = activeInstance
// prevActiveInstance 和当前的 vm 是一个父子关系
activeInstance = vm
// 这个 vnode 是通过 vm._render() 返回的组件渲染 VNode
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
// 最后就是调用 __patch__ 渲染 VNode 了
// 组件在patch过程中又调用了createElm方法,又回到了本节一开始的过程
// createElm(vnode, insertedVnodeQueue) 往下分析createElm
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
// 可以看到 vm.$parent 就是用来保留当前 vm 的父实例,并且通过 parent.$children.push(vm) 来把当前的 vm 存储到父实例的 $children 中。
export function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
// ...
}
// createElm
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
// createComponent返回值是 false
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
// 先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历的过程中,如果遇到子 VNode 是一个组件的 VNode,则重复本节开始的过程,这样通过一个递归的方式就可以完整地构建了整个组件树
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
/* istanbul ignore if */
if (__WEEX__) {
// ...
} else {
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)
}
}
总结
当我们通过createComponent
创建了VNode
,接下来我们会通过vm._update
中的vm._patch
将VNode
转换为真正的DOM
节点。_patch
的核心是_createElm
函数。
在_createElm
中我们先使用createComponent
判断返回值:
- 如果是子组件
VNode
就返回true,所以在_createElm
不会进行下一步; - 如果是普通
Vnode
就先创建一个父节点占位符,然后再遍历所有子VNode
递归调用createElm
,在遍历的过程中,如果遇到子VNode
是一个组件的VNode
,则重复上一个过程。
在createComponent
,如果是子组件,就调用在一开始用createComponent
方法中挂载的init
钩子,在内部通过 createComponentInstanceForVnode
调用子构造器,创建一个Vue
的实例,_init
中使用mergeOptions
合并配置项然后调用 $mount
方法挂载子组件,之后再执行_render
和_update
去渲染VNode了
,然后又回到了_createElm
方法中。
在完成组件的整个 patch
过程后,最后执行 insert(parentElm, vnode.elm, refElm)
完成组件的 DOM 插入,如果组件 patch
过程中又创建了子组件,那么DOM 的插入顺序是先子后父。
一个组件的 VNode 是如何创建、初始化、渲染的过程也就介绍完毕了。
这一节我们提到了mergeoptions
方法,在学习这个方法前,我们先来学习一下mergeoptions
中的resolveConstructorOptions
函数。