Ps: suggested PC viewing, mobile code highlighting disorder

In the last chapter, when we looked at the implementation of createElement, it ended up calling the _createElement method. There was a bit of logic to the argument tag. If it was a normal HTML tag, A normal VNode is instantiated, otherwise a component VNode is created using the createComponent method.

// src/core/vdom/create-element.js

export function _createElement (

  context: Component,

tag? :string | Class<Component> | Function | Object.

data? : VNodeData,

children? :any.

normalizationType? :number

) :VNode | Array<VNode
{

  // ...

  

  if (typeof tag === 'string') {

    // ...

  } else {

    vnode = createComponent(tag, data, context, children);

  }

}

Copy the code

For example, when developing with vue-CLI scaffolding, we use new vue:

new Vue({

  renderh= > h(App),

}).$mount('#app')

Copy the code

We pass createElement a component, which goes to the else logic and creates a component by calling createComponent

Now take a look at createComponent, which is defined in SRC /core/vdom/create-component.js

// 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

  }



  // Core logic 1: Create a subclass constructor

  const baseCtor = context.$options._base

  if (isObject(Ctor)) {

    Ctor = baseCtor.extend(Ctor)

  }

  if (typeofCtor ! = ='function') {

    if(process.env.NODE_ENV ! = ='production') {

      warn(`Invalid Component definition: The ${String(Ctor)}`, context)

    }

    return

  }



  // Other logic that you don't need to worry about for now:

  // 1. Asynchronous components

  // 2. If a global mixin is applied after the component constructor is created, the constructor options is resolved

  // 3. Convert component V-model to props & Events

  // 4

  // 5. Functional components

  // 6. Handling of event listening

  // 7. Abstract component processing



  // Core logic 2: Install component hook functions

  installComponentHooks(data)



  // Core logic 3: Instantiate a VNode

  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 logic...



  return vnode

}

Copy the code

The comments highlight three core logic that need to be addressed in this chapter:

  • Create the subclass constructor
  • Install component hook functions
  • Instantiation VNode

1. Create the subclass constructor

BaseCtor is the big Vue constructor. This is defined at the initial Vue initialization stage in initGlobalAPI function SRC /core/global-api/index.js:

Vue.options._base = Vue

Copy the code

If you’re careful, you’ll notice that vue. options is defined and that our createComponent takes context.$options. In fact in the SRC/core/instance/init. _init function in js have so a logic:

vm.$options = mergeOptions(

  resolveConstructorOptions(vm.constructor),

  options || {},

  vm

)

Copy the code

This extends some options on Vue to vm.$options, so we can get the Vue constructor via vm.$options._base. The mergeOptions implementation will be discussed in the following sections. For now, it is only necessary to understand that the function of mergeOptions is to merge the options of the Vue constructor and the options passed by the user into vm.$options.

In addition, our component is usually a normal object, such as a normal object returned after processing our single-file component with vue-loader

So isObject(Ctor) is true, and then we create the constructor with baseCtor. Extend (Ctor), which is vue.extend

1.1 the Vue. The extend

The vue. extend function is defined in initExtend, which is defined in initGlobalAPI.

// src/core/global-api/extend.js



export function initExtend (Vue: GlobalAPI{

  // Each constructor instance, including Vue, has a unique CID that can be used to cache child constructors

  Vue.cid = 0

  let cid = 1



  Vue.extend = function (extendOptions: Object) :Function {

    extendOptions = extendOptions || {}

    const Super = this

    const SuperId = Super.cid

    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})

    // Whether to return the cache constructor

    if (cachedCtors[SuperId]) {

      return cachedCtors[SuperId]

    }



    const name = extendOptions.name || Super.options.name

    if(process.env.NODE_ENV ! = ='production' && name) {

      validateComponentName(name) / / validate the name

    }



    const Sub = function VueComponent (options{

      // Executing this._init logic again takes us to the initialization logic of the Vue instance, which instantiates the child components in a later section.

      this._init(options)

    }

    // Prototype inheritance

    Sub.prototype = Object.create(Super.prototype)

    Sub.prototype.constructor = Sub

    Sub.cid = cid++

    Sub.options = mergeOptions(

      Super.options,

      extendOptions

    )

    Sub['super'] = Super



    // ...



    // Cache constructor

    cachedCtors[SuperId] = Sub

    return Sub

  }

}

Copy the code
  • Define the subclass constructorSub, based on the prototype chain inherited fromVue
  • Then theSubThe object itself extends some properties, such as:
    • Extend options to add global API
    • For the configurationpropscomputedInitialization is done.
    • aboutinitPropsThere will be a special chapter on it, so let’s make an impression
  • Cache constructor to avoid multiple executionsVue.extendIs constructed repeatedly for the same child component.

So when we instantiate Sub, the this._init logic is executed, again going to the initialization logic of the Vue instance, which will be covered in a later section.

const Sub = function VueComponent (options{

  this._init(options)

}

Copy the code

2. Install component hook functions

Installation is used to execute hook functions during VNode patch execution, which will be described later in patch.

InstallComponentHooks function logic:

// src/core/vdom/create-component.js

function installComponentHooks (data: VNodeData{

  const hooks = data.hook || (data.hook = {})

  / / hooksToMerge is Object. Keys (componentVNodeHooks)

  for (let i = 0; i < hooksToMerge.length; i++) {

    const key = hooksToMerge[i] // init, prepatch, insert, destroy

    const existing = hooks[key]

    const toMerge = componentVNodeHooks[key]

    if(existing ! == toMerge && ! (existing && existing._merged)) {

      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge

    }

  }

}

Copy the code

To understand this function, you need to know two more things:

  • ComponentVNodeHooks object
  • MergeHook function

2.1 componentVNodeHooks

The Virtual DOM used by vue. js refers to the open source library SnabbDOM. One of its features is that the hook functions of various timing are exposed in the patch process of VNode, which is convenient for us to do some extra things. Vue.js also makes full use of this. There are several hook functions implemented during the initialization of a Component VNode:

// src/core/vdom/create-component.js

// inline hooks to be invoked on component VNodes during patch

const componentVNodeHooks = {

  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {

    // ...

  },



  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {

    // ...

  },



  insert (vnode: MountedComponentVNode) {

    // ...

  },



  destroy (vnode: MountedComponentVNode) {

    // ...

  }

}

Copy the code

2.2 mergeHook

// src/core/vdom/create-component.js

function mergeHook (f1: any, f2: any) :Function {

  const merged = (a, b) = > {

    f1(a, b)

    f2(a, b)

  }

  merged._merged = true

  return merged

}

Copy the code

MergeHook () {componentVNodeHooks (); data.hooks (); true;

3. Instantiate the VNode

// 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 
{

  // ...



  // Core logic 3: Instantiate a VNode

  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

}

Copy the code
  • throughnew VNodeInstantiate avnodeAnd return.
  • And the normal element nodevnodeDifferent,The component’svnodeThere is nochildrenThis is the key pointpatchWe’ll talk about that as we go along.
  • The seventh parameter iscomponentOptionsIn thepatchThe process can be passednew vnode.componentOptions.CtorTo instantiate the child component constructor

conclusion

In this section we looked at the createComponent implementation and learned that it uses three key logic to render a component: create subclass constructors, install component hook functions, and instantiate vNodes. CreateComponent returns the component VNode, which also goes to the vm._update method and executes the patch function, which was briefly analyzed in the previous chapter and will be further analyzed in the next section.