createComponent
在我们这一章传入的是一个 App 对象,它本质上是一个 Component
类型,那么它会走到上述代码的 else 逻辑,直接通过 createComponent
方法来创建 vnode
。所以接下来我们来看一下 createComponent
方法的实现,它定义在 src/core/vdom/create-component.js
文件中:
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// if at this stage it's not a constructor or an async component factory,
// reject.
if (typeof Ctor !== 'function') {
if (process.env.NODE_ENV !== 'production') {
warn(`Invalid Component definition: ${String(Ctor)}`, context)
}
return
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
}
// extract props
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
// functional component
if (isTrue(Ctor.options.functional)) {
return createFunctionalComponent(Ctor, propsData, data, context, children)
}
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// install component management hooks onto the placeholder node
installComponentHooks(data)
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
// Weex specific: invoke recycle-list optimized @render function for
// extracting cell-slot template.
// https://github.com/Hanks10100/weex-native-directive/tree/master/component
/* istanbul ignore if */
if (__WEEX__ && isRecyclableComponent(vnode)) {
return renderRecyclableComponentTemplate(vnode)
}
return vnode
}
可以看到,createComponent
的逻辑也会有一些复杂,但是分析源码比较推荐的是只分析核心流程,分支流程可以之后针对性的看,所以这里针对组件渲染这个 case 主要就 3 个关键步骤:
构造子类构造函数,安装组件钩子函数和实例化 vnode
。
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
我们在编写一个组件的时候,通常都是创建一个普通对象,还是以我们的 App.vue 为例,代码如下:
// this is used to identify the "base" constructor to extend all plain-object
// components with in Weex's multi-instance scenarios.
细心的同学会发现,这里定义的是 Vue.option
,而我们的 createComponent
取的是 context.$options
,实际上在 src/core/instance/init.js
里 Vue 原型上的 _init
函数中有这么一段逻辑:
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
这样就把 Vue 上的一些 option
扩展到了 vm.$option 上,所以我们也就能通过 vm.$options._base
拿到 Vue 这个构造函数了。mergeOptions
的实现我们会在后续章节中具体分析,现在只需要理解它的功能是把 Vue 构造函数的 options
和用户传入的 options
做一层合并,到 vm.$options
上。
在了解了 baseCtor
指向了 Vue 之后,我们来看一下 Vue.extend
函数的定义,在 src/core/global-api/extend.js
中。
Vue.extend
的作用就是构造一个 Vue
的子类,它使用一种非常经典的原型继承的方式把一个纯对象转换一个继承于 Vue
的构造器 Sub
并返回,然后对 Sub
这个对象本身扩展了一些属性,如扩展 options
、添加全局 API 等;并且对配置中的 props
和 computed
做了初始化工作;最后对于这个 Sub
构造函数做了缓存,避免多次执行 Vue.extend
的时候对同一个子组件重复构造。
const Sub = function VueComponent (options) {
this._init(options)
}
// install component management hooks onto the placeholder node
installComponentHooks(data)
我们之前提到 Vue.js 使用的 Virtual DOM 参考的是开源库 snabbdom,它的一个特点是在 VNode 的 patch 流程中对外暴露了各种时机的钩子函数,方便我们做一些额外的事情,Vue.js 也是充分利用这一点,在初始化一个 Component 类型的 VNode 的过程中实现了几个钩子函数:
整个 installComponentHooks
的过程就是把 componentVNodeHooks
的钩子函数合并到 data.hook
中,在 VNode 执行 patch
的过程中执行相关的钩子函数,具体的执行我们稍后在介绍 patch
过程中会详细介绍。这里要注意的是合并策略,在合并过程中,如果某个时机的钩子已经存在 data.hook
中,那么通过执行 mergeHook
函数做合并,这个逻辑很简单,就是在最终执行的时候,依次执行这两个钩子函数即可。
const name = Ctor.options.name || tag
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
最后一步非常简单,通过 new VNode
实例化一个 vnode
并返回。需要注意的是和普通元素节点的 vnode
不同,组件的 vnode
是没有 children
的,这点很关键,在之后的 patch
过程中我们会再提。
这一节我们分析了 createComponent
的实现,了解到它在渲染一个组件的时候的 3 个关键逻辑:构造子类构造函数,安装组件钩子函数和实例化 vnode
。createComponent
后返回的是组件 vnode
,它也一样走到 vm._update
方法,进而执行了 patch
函数,我们在上一章对 函数做了简单的分析,那么下一节我们会对它做进一步的分析。