createComponent

    在我们这一章传入的是一个 App 对象,它本质上是一个 Component 类型,那么它会走到上述代码的 else 逻辑,直接通过 createComponent 方法来创建 vnode。所以接下来我们来看一下 createComponent 方法的实现,它定义在 src/core/vdom/create-component.js 文件中:

    1. export function createComponent (
    2. Ctor: Class<Component> | Function | Object | void,
    3. data: ?VNodeData,
    4. context: Component,
    5. children: ?Array<VNode>,
    6. tag?: string
    7. ): VNode | Array<VNode> | void {
    8. if (isUndef(Ctor)) {
    9. return
    10. }
    11. const baseCtor = context.$options._base
    12. // plain options object: turn it into a constructor
    13. if (isObject(Ctor)) {
    14. Ctor = baseCtor.extend(Ctor)
    15. }
    16. // if at this stage it's not a constructor or an async component factory,
    17. // reject.
    18. if (typeof Ctor !== 'function') {
    19. if (process.env.NODE_ENV !== 'production') {
    20. warn(`Invalid Component definition: ${String(Ctor)}`, context)
    21. }
    22. return
    23. }
    24. // async component
    25. let asyncFactory
    26. if (isUndef(Ctor.cid)) {
    27. asyncFactory = Ctor
    28. Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
    29. if (Ctor === undefined) {
    30. // return a placeholder node for async component, which is rendered
    31. // as a comment node but preserves all the raw information for the node.
    32. // the information will be used for async server-rendering and hydration.
    33. return createAsyncPlaceholder(
    34. asyncFactory,
    35. data,
    36. context,
    37. children,
    38. )
    39. }
    40. }
    41. data = data || {}
    42. // resolve constructor options in case global mixins are applied after
    43. // component constructor creation
    44. resolveConstructorOptions(Ctor)
    45. // transform component v-model data into props & events
    46. if (isDef(data.model)) {
    47. transformModel(Ctor.options, data)
    48. }
    49. // extract props
    50. const propsData = extractPropsFromVNodeData(data, Ctor, tag)
    51. // functional component
    52. if (isTrue(Ctor.options.functional)) {
    53. return createFunctionalComponent(Ctor, propsData, data, context, children)
    54. }
    55. // extract listeners, since these needs to be treated as
    56. // child component listeners instead of DOM listeners
    57. const listeners = data.on
    58. // replace with listeners with .native modifier
    59. // so it gets processed during parent component patch.
    60. data.on = data.nativeOn
    61. if (isTrue(Ctor.options.abstract)) {
    62. // abstract components do not keep anything
    63. // other than props & listeners & slot
    64. // work around flow
    65. const slot = data.slot
    66. data = {}
    67. if (slot) {
    68. data.slot = slot
    69. }
    70. }
    71. // install component management hooks onto the placeholder node
    72. installComponentHooks(data)
    73. const name = Ctor.options.name || tag
    74. const vnode = new VNode(
    75. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    76. data, undefined, undefined, undefined, context,
    77. { Ctor, propsData, listeners, tag, children },
    78. asyncFactory
    79. )
    80. // Weex specific: invoke recycle-list optimized @render function for
    81. // extracting cell-slot template.
    82. // https://github.com/Hanks10100/weex-native-directive/tree/master/component
    83. /* istanbul ignore if */
    84. if (__WEEX__ && isRecyclableComponent(vnode)) {
    85. return renderRecyclableComponentTemplate(vnode)
    86. }
    87. return vnode
    88. }

    可以看到,createComponent 的逻辑也会有一些复杂,但是分析源码比较推荐的是只分析核心流程,分支流程可以之后针对性的看,所以这里针对组件渲染这个 case 主要就 3 个关键步骤:

    构造子类构造函数,安装组件钩子函数和实例化 vnode

    1. const baseCtor = context.$options._base
    2. // plain options object: turn it into a constructor
    3. if (isObject(Ctor)) {
    4. Ctor = baseCtor.extend(Ctor)
    5. }

    我们在编写一个组件的时候,通常都是创建一个普通对象,还是以我们的 App.vue 为例,代码如下:

    1. // this is used to identify the "base" constructor to extend all plain-object
    2. // components with in Weex's multi-instance scenarios.

    细心的同学会发现,这里定义的是 Vue.option,而我们的 createComponent 取的是 context.$options,实际上在 src/core/instance/init.js 里 Vue 原型上的 _init 函数中有这么一段逻辑:

    1. vm.$options = mergeOptions(
    2. resolveConstructorOptions(vm.constructor),
    3. options || {},
    4. vm
    5. )

    这样就把 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 等;并且对配置中的 propscomputed 做了初始化工作;最后对于这个 Sub 构造函数做了缓存,避免多次执行 Vue.extend 的时候对同一个子组件重复构造。

    1. const Sub = function VueComponent (options) {
    2. this._init(options)
    3. }
    1. // install component management hooks onto the placeholder node
    2. installComponentHooks(data)

    我们之前提到 Vue.js 使用的 Virtual DOM 参考的是开源库 snabbdom,它的一个特点是在 VNode 的 patch 流程中对外暴露了各种时机的钩子函数,方便我们做一些额外的事情,Vue.js 也是充分利用这一点,在初始化一个 Component 类型的 VNode 的过程中实现了几个钩子函数:

    整个 installComponentHooks 的过程就是把 componentVNodeHooks 的钩子函数合并到 data.hook 中,在 VNode 执行 patch 的过程中执行相关的钩子函数,具体的执行我们稍后在介绍 patch 过程中会详细介绍。这里要注意的是合并策略,在合并过程中,如果某个时机的钩子已经存在 data.hook 中,那么通过执行 mergeHook 函数做合并,这个逻辑很简单,就是在最终执行的时候,依次执行这两个钩子函数即可。

    1. const name = Ctor.options.name || tag
    2. const vnode = new VNode(
    3. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    4. data, undefined, undefined, undefined, context,
    5. { Ctor, propsData, listeners, tag, children },
    6. asyncFactory
    7. )
    8. return vnode

    最后一步非常简单,通过 new VNode 实例化一个 vnode 并返回。需要注意的是和普通元素节点的 vnode 不同,组件的 vnode 是没有 children 的,这点很关键,在之后的 patch 过程中我们会再提。

    这一节我们分析了 createComponent 的实现,了解到它在渲染一个组件的时候的 3 个关键逻辑:构造子类构造函数,安装组件钩子函数和实例化 vnodecreateComponent 后返回的是组件 vnode,它也一样走到 vm._update 方法,进而执行了 patch 函数,我们在上一章对 函数做了简单的分析,那么下一节我们会对它做进一步的分析。