— This article is taken from my official account “Sun Wukong, Don’t talk Nonsense”

Componentization is a necessity in the MVVM framework.

Through componentization, the page is cut, and the corresponding logic is abstracted into an independent module.

So how does componentization work in Vue? We illustrate this process in several points: the component declaration process, the implementation of Vue.component(), the component creation and mounting process.

Component declarations

The declaration of Vue.component() is a global API, and we can look at the code of initGlobalAPI() in /core/index.js:

import { initUse } from './use'
import { initMixin } from './mixin'
import { initExtend } from './extend'
import { initAssetRegisters } from './assets'

export functioninitGlobalAPI (Vue: GlobalAPI) { //... Omit initUse(Vue) initMixin(Vue) initExtend(Vue) initAssetRegisters(Vue)}Copy the code

Vue.component() is not explicitly declared in the code, and the declaration process is dynamic. This is done in initAssetRegisters() :

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '.. /util/index'

export function initAssetRegisters (Vue: GlobalAPI) {

  // ASSET_TYPES : ['component'.'directive'.'filter']
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if(! definition) {return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if* /if(process.env.NODE_ENV ! = ='production' && type= = ='component') {validateComponentName(id)} // Special handling of componentif (type= = ='component'&& isPlainObject (definition)) {/ / specified name definition. The name = definition. The name | | id / / conversion component configuration object for the constructor definition = this.options._base.extend(definition) }if (type= = ='directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition}}'components'][id] = Ctor // You can use this.options elsewhere in the world.type + 's'][id] = definition
        return definition
      }
    }
  })
}
Copy the code

Let’s use an example to illustrate the code process:

// Define a new component called button-counter Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
Copy the code

The component declaration process is to get the name of the component, then {data:”…” , template: “… } to a constructor, and finally, register the constructor for this component with this.options.components.

This.options.com ponents is first checked to see if this component exists when it is used by other components, thus implementing global registration.

Create and mount custom components

So, we want to ask: when was this custom component created?

Render process

The root component is created first, and the first time _render() gets the VNode structure of the entire tree, which must include the creation of the child components. So where does the creation of the child component go? Let’s review the root component creation process:

new Vue() => $mount() => vm._render() => _createElement() => createComponent()
Copy the code

_createElement() is triggered during _render(). Inside this function, createComponent() is triggered if it is a custom component, and is queried as follows:

export function_createElement ( context: Component, tag? : string | Class<Component> | Function | Object, data? : VNodeData, children? : any, normalizationType? : number ): VNode | Array<VNode> { ... // omit // core: VNode generation process // The tag passed in May be native HTML tags, or may be user-defined tagslet vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnodeNs) | | config. GetTagNamespace (tag) / / is native to retain tag, create VNode directlyif (config.isReservedTag(tag)) {
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    }else if((! data||! data.pre)&&isDef(Ctor=resolveAsset(context.$options.'components'This.options.com ponents = this.options.components = this.options.components = this.options.components = this.options.components = this.options.components = this.options.components VNode = createComponent(Ctor, data, context, children, tag)}else {
      //
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else{ // direct component options / constructor vnode = createComponent(tag, data, context, children) } ... / / omit}Copy the code

So let’s look at createComponent(), whose main job is to get a VNode from the constructor Ctor:

export functioncreateComponent ( Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ? Array<VNode>, tag? : : string) VNode | Array < VNode > | void {/ / ellipsis... // Install the component's management hooks on the node. Will call installComponentHooks patch for the first time in the root component (data) / / return VNode const name = Ctor. Options. The name | | tag const VNode = new VNode ( `vue-component-${Ctor.cid}${name ? `-${name}` : ' '}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, AsyncFactory) // omit...return vnode
}
Copy the code

So what does installComponentHooks() do? What are component management hooks? Let’s see:

Const componentVNodeHooks = {// Init (vnode: VNodeWithData, hydrating: Boolean):? boolean {if( vnode.componentInstance && ! vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) }else{// Create a custom VueComponent instance, And corresponding VNode related child = vnode.com ponentInstance = const createComponentInstanceForVnode (VNode, activeInstance) / / created, Mount the child immediately.$mount(hydrating ? vnode.elm : undefined, hydrating) } }, prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { ... }, insert (vnode: MountedComponentVNode) { ... }, destroy (vnode: MountedComponentVNode) { ... }} const hooksToMerge = Object.keys(componentVNodeHooks) {// Put custom component-related hooks in the data.hook of the generated VNode. Custom components are created using these hooks and mounted immediatelyfunction installComponentHooks (data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    const existing = hooks[key]
    const toMerge = componentVNodeHooks[key]
    if(existing ! == toMerge && ! (existing && existing._merged)) { hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge } } }Copy the code

Init (),prepatch(),insert(),destroy(). Watch init() : Create a VueComponent instance of the custom component, associate it with the corresponding VNode, and immediately execute the $mount() method.

The update process

In installComponentHooks(), you simply place the four administrative hooks in the data.hook of the generated VNode. The corresponding administrative hook call is made during patch() during the first update() execution. Detailed process/core/instance/vdom/patch. The js createElm () :

  functionCreateElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {// delete... // The child component generation processif (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return} // Native tag generation process // omit... }functionCreateComponent (vnode, insertedVnodeQueue, parentElm, refElm) {// Get datalet i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      if(isDef(I = i.hook) &&isdef (I = i.init)) {// I is the vnode.data.hook.init() method, where to create a custom component instance and complete the mount // see details'./patch.js componentVNodeHooks()'
        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 returnThe element and be done. // Determine whether the custom component is instantiatedif(isDef(vnode.componentInstance)) {// After instantiating a custom component, // Insert (parentElm, vnode.elm, refElm)if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true}}}Copy the code

As you can see, the custom component creation process is in the patch.js createComponent() method. The custom component is created here by calling the vnode.data.hook.init() method mentioned above (linked to the previous paragraph of this article) and immediately calling $mount().

At this point the following conclusions can be understood:

Components are created top-down

Components are mounted from bottom to top


Vue source code interpretation

(1) : Vue constructor and initialization process

(II) : Data response and implementation

(3) : array responsive processing

(iv) Vue’s asynchronous update queue

(5) The introduction of virtual DOM

(VI) Data update algorithm — Patch algorithm

(7) : realization of componentization mechanism

(8) : computing properties and listening properties

The Optimize stage of the compilation process

Vue

Vue’s error handling mechanism

Parse the working process of Vue in handwritten code

Vue Router handwriting implementation