As analyzed in the previous chapter, when we created the component VNode through createComponent, we then went to vm._update and executed vm.__patch__ to convert the VNode into a real DOM node. This process is familiar, but for a normal VNode, let’s take a look at how component VNodes are different.

Examples throughout:

// index.html
<body>
  <div id="app"></div>
</body>

// main.js
new Vue({
  render: h => h(App),
}).$mount('#app')

// App.vue
<template>
  <div id="app">
    hello Vue
  </div>
</template>
<script>
export default {
  name: "app"
};
</script>
Copy the code

Because of the complexity of this lesson, I drew a simple flow chart for easy understanding

1. Three concepts

Before getting into the body of the text, there are two concepts that need to be understood to facilitate learning

1.1 Two VNodes

  • A placeholdervnode:vm.$vnodeOnly component instances. in_renderProcess assignment
  • Apply colours to a drawingvnode:vm._vnodeIt can be mapped directly to realityDOM. in_updateProcess assignment
  • They are father-son relationships:vm._vnode.parent = vm.$vnode

For example, in this article’s example:

  • When the rootVueThe instancerenderThe root instance will be generated when the function completes execution_vnode
  • Due to thecreateElementIt goes straight backAppComponent, so root instance_vnodealsoAppThe component’s$vnode

  • forAppComponent for his placeholdervnodeAnd rendervnodeIt’s actually a father-son relationship

1.2 activeInstance

The entire initialization is a deep traversal process. In instantiating a child component, it needs to know what the Vue instance of the current context is and treat it as the parent Vue instance of the child component.

It is a global variable, defined in SRC/core/instance/lifecycle. Js

// src/core/instance/lifecycle.js



export let activeInstance: any = null



export function setActiveInstance(vm: Component{

  const prevActiveInstance = activeInstance

  activeInstance = vm

  return (a)= > {

    activeInstance = prevActiveInstance

  }

}



export function lifecycleMixin (Vue: Class<Component>{

  Vue.prototype._update = function (vnode: VNode, hydrating? :boolean{

    const vm: Component = this

    

    // Save the activeInstance with prevActiveInstance before calling __Patch__

    // Then assign the current instance VM to activeInstance

    const restoreActiveInstance = setActiveInstance(vm)

        

    // vm.__patch__...

    

    // Restore activeInstance after __patch__ is executed

    restoreActiveInstance()

  }

}

Copy the code

During vm._update, assign the current VM to the activeInstance and retain the previous activeInstance with prevActiveInstance. PrevActiveInstance has a parent-child relationship with the current VM

When a VM instance completes patch or update of all its subtrees, activeInstance returns to its parent instance

This perfectly ensures that we pass in the parent Vue instance of the current child as we instantiate the child throughout the depth traversal.

2. The patch components

In the last chapter, we actually analyzed the details of the component in the Render stage, and this chapter analyzes the details in the patch stage

When the patch phase is complete, createElm is called to create the element node, which contains the following logic:

// src/core/vdom/patch.js



function createElm (

  vnode,

  insertedVnodeQueue,

  parentElm,

  refElm,

  nested,

  ownerArray,

  index

{

  // ...

  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {

    return

  }

  // ...

}

Copy the code

If the createComponent call returns true, execution of createElm ends.

The vNode argument passed to createComponent is $vNode for the App component, so the component can be successfully created by createComponent and createElm will not proceed. The createComponent function is defined as follows:

// src/core/vdom/patch.js



export function createPatchFunction (backend{

  // ...

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm{

    let i = vnode.data

    if (isDef(i)) {

      if (isDef(i = i.hook) && isDef(i = i.init)) {

        // 2.1 Execute init hook. A subinstance has been created and mounted

        i(vnode, false /* hydrating */)

      }

      if (isDef(vnode.componentInstance)) {

        // 2.2 Setting the placeholder vnode elm

        initComponent(vnode, insertedVnodeQueue)

        // 2.3 Mounting subcomponents

        insert(parentElm, vnode.elm, refElm)



        // ...



        return true

      }

    }

  }

  // ...

}

Copy the code
  • ifStatement means to judgevnode.data.hook.initDoes it exist? HerevnodeIt’s a componentVNode, then the condition is satisfied and we getiisinitHook function. The main functions are:
    • instantiationAppChild components
    • Perform child component mountchild.$mount
  • initComponentIt is mainly implementedvnode.elm = vnode.componentInstance.$el
  • insertIs to insert its component intobody

2.1 Executing init hook

Recall from the previous chapter that installComponentHooks is called when createComponent is executed to install four hook functions for vNode.data.hook. Review the code for the init hook function, which is defined in SRC /core/vdom/create-component.js:

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



import { activeInstance } from '.. /instance/lifecycle'



const componentVNodeHooks = {

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

    if (

      vnode.componentInstance &&

! vnode.componentInstance._isDestroyed &&

      vnode.data.keepAlive

    ) {

      // keepAlive related...

    } else {

      Create an instance of Vue

      const child = vnode.componentInstance = createComponentInstanceForVnode(

        vnode,

        activeInstance

      )

      // The child component is mounted

      child.$mount(hydrating ? vnode.elm : undefined, hydrating)

    }

  },

}

Copy the code
  • aboutkeepAliveWe don’t care about the logic of
  • throughcreateComponentInstanceForVnodeCreate a Vue instance with two arguments:
    • vnodeSaid that the currentAppA placeholder for a componentVNode
    • activeInstanceAccording to the rootVueThe instance
  • The returned child component instance is saved toAppA placeholder for a componentvnodecomponentInstanceAttribute.
  • through$mountMount child components

2.1.1 Creating Sub-Component Instances

Let’s look at createComponentInstanceForVnode function is how to create a component instance, it defined in SRC/core/vdom/create – component. Js

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



export function createComponentInstanceForVnode (

  vnode: any.

  parent: any.// activeInstance in lifecycle state

) :Component 
{

  const options: InternalComponentOptions = {

    _isComponent: true.

    _parentVnode: vnode,

    parent

  }



  // Inline-template correlation...



  return new vnode.componentOptions.Ctor(options)

}

Copy the code
  • createoptionsobject
    • _isComponenttrueIndicates that it is a component
    • _parentVnodeRepresents the vNode of the current component, i.ePlaceholder vnode
    • parentRepresents the currently active component instance, that isRoot Vue instance
  • instantiationAppChild components
    • vnode.componentOptions.CtorIs the constructor of the child component
    • weIn the previous sectionIt is actually inherited fromVueA constructor forSubSo it is equivalent tonew Sub(options)
const sub = function VueComponent({

  // Execute this._init logic again to the initialization logic of the Vue instance

  this._init(options)

}

Copy the code

This process has some and pick out different places need to say, before the code in the SRC/core/instance/init. In js:

// src/core/instance/init.js



Vue.prototype._init = function (options? :Object{

  const vm: Component = this

  if (options && options._isComponent) {

    // The child component mergeOptions

    initInternalComponent(vm, options)

  } else {

    // mergeOptions...

  }

  

  // ...

  initLifecycle(vm)

  // ...

  

  if (vm.$options.el) {

    vm.$mount(vm.$options.el)

  } 

}

Copy the code
  • mergeoptionsThere are changes in the process of_isComponenttrueSo we came toinitInternalComponentProcess. mergecreateComponentInstanceForVnodeFunctionoptionsobject
  • performinitLifecycleTo establish relationships between parent and child instances
  • Component initialization does not passelSo the component is taking over by itself$mountThe process of

Here’s a quick look at the initInternalComponent function:

// src/core/instance/init.js



export function initInternalComponent (vm: Component, options: InternalComponentOptions{

  // Create the vm.$options object

  const opts = vm.$options = Object.create(vm.constructor.options)

  const parentVnode = options._parentVnode

  opts.parent = options.parent // Save parent Vue instance

  opts._parentVnode = parentVnode // Save the component placeholder vnode



  // ...

}

Copy the code

Take a quick look at the initLifecycle function

// src/core/instance/lifecycle.js



export function initLifecycle (vm: Component{

  const options = vm.$options



  // Find the first non-abstract parent

  let parent = options.parent

  if(parent && ! options.abstract) {

    while (parent.$options.abstract && parent.$parent) {

      parent = parent.$parent

    }

    // Save the parent-child instance relationship

    parent.$children.push(vm)

  }



  vm.$parent = parent

  vm.$root = parent ? parent.$root : vm

  vm.$children = []

  // ...

}

Copy the code

Push (vm) is used to store the current VM in the parent instance of $children.

2.1.2 Mounting Subcomponents

After _init is done for the instantiation of the child component, it is then executed

child.$mount(hydrating ? vnode.elm : undefined, hydrating) 
Copy the code

Hydrating here represents server-side rendering, and we are only considering client-side rendering, so this is equivalent to execution

child.$mount(undefined, false)
Copy the code

It ends up calling the mountComponent method, which executes the vm._render() method:

// src/core/instance/render.js



Vue.prototype._render = function () :VNode {

  const vm: Component = this

  const { render, _parentVnode } = vm.$options



  // Save the placeholder vnode/shell node

  vm.$vnode = _parentVnode



  let vnode

  try {

    // Create a component render vNode using the render function

    vnode = render.call(vm._renderProxy, vm.$createElement)

  } catch (e) {

    // ...

  }

  // Save the placeholder vnode to render vNode's parent property

  vnode.parent = _parentVnode

  return vnode

}

Copy the code
  • Save the placeholdervnodeAppComponent instance ofvm.$vnode
  • callrenderFunction generated rendervnode
  • Save the placeholdervnodeAnd rendervnodeFather and son relationship.vm._vnode.parent = vm.$vnode

We know that after vm._render is executed to generate vNodes, vm._update is executed to render vNodes. Take a look at what need to pay attention to in the process of component rendering, vm. _update defined in SRC/core/instance/lifecycle. In js:

// src/core/instance/lifecycle.js



export function lifecycleMixin (Vue: Class<Component>{

  Vue.prototype._update = function (vnode: VNode, hydrating? :boolean{

    const vm: Component = this

    const prevEl = vm.$el

    const prevVnode = vm._vnode

    

    // activeInstance saved as the instance of the current App component...

    

    // Save the render vNode generated by render

    vm._vnode = vnode



    if(! prevVnode) {

      // First render

      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)

    } else {

      // ...

    }

    

    // Restore activeInstance to root Vue instance...    

  }

}

Copy the code
  • Save byrenderGenerated rendervnode
  • activeInstanceUpdated toAppComponent instance
  • call__patch__Apply colours to a drawingVNode.
    • The return result isAppRendering of child componentsvnodeelm, that is,vnode.elm
    • Assign the result toAppOf the child component instance$el
  • restoreactiveInstanceAs the rootVueThe instance

When the __patch__ method is called, the patch method is executed. The first two arguments passed in are:

  • oldVnode:AppThe child component$elAt this point isundefined
  • vnode:AppRendering of child componentsvnode
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)

 

function patch (oldVnode, vnode, hydrating, removeOnly{

  // ...

  let isInitialPatch = false

  const insertedVnodeQueue = []



  if (isUndef(oldVnode)) {

    // empty mount (likely as component), create new root element

    isInitialPatch = true

    createElm(vnode, insertedVnodeQueue)

  } else {

    // ...

  }

  // ...

  return vnode.elm

}



Copy the code
  • Performed againcreateElmMethod, which generates a rendervnodeelm
  • returnAppComponent renderingvnodeelm

Take a look at the createElm method and notice that we only pass two arguments, so parentElm is undefined. Let’s look at its definition:

// src/core/vdom/patch.js



export function createPatchFunction (backend{

  // ...

  function createElm (

    vnode, / / render vnode

    insertedVnodeQueue,

    parentElm, // undefined

    refElm,

    nested,

    ownerArray,

    index

  
{

    // ...

    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {

      return

    }



    const data = vnode.data

    const children = vnode.children

    const tag = vnode.tag

    if (isDef(tag)) {

      // ...



      vnode.elm = vnode.ns

        ? nodeOps.createElementNS(vnode.ns, tag)

        : nodeOps.createElement(tag, vnode)

      setScope(vnode)



      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)

    }

  }

  // ...

}

Copy the code
  • It’s executed againcreateComponentDetermines that the component root node is a normal element, so returnsfalse
  • Start by creating a parent node placeholder
  • And then I iterate over all of themVNodeRecursive callscreateElm
  • In the process of traversal, if you encounter a sonVNodeIt’s a componentVNode, repeat the process started in this section
  • Because we passed it in at this timeparentElmIt’s empty, so actuallyinsert(parentElm, vnode.elm, refElm)Without doing anything, how does the child component get inserted? Look down with this question

2.2. initComponent

After the init hook is executed, initComponent is executed

// src/core/vdom/patch.js



export function createPatchFunction (backend{

  // ...

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm{

    let i = vnode.data

    if (isDef(i)) {

      if (isDef(i = i.hook) && isDef(i = i.init)) {

        // 2.1 Execute init hook. A subinstance has been created and mounted

        i(vnode, false /* hydrating */)

      }

      if (isDef(vnode.componentInstance)) {

        // 2.2 Setting the placeholder vnode elm

        initComponent(vnode, insertedVnodeQueue)

        // 2.3 Mounting subcomponents

        insert(parentElm, vnode.elm, refElm)



        // ...



        return true

      }

    }

  }

  // ...

}

Copy the code

Look at the initComponent function:

// src/core/vdom/patch.js



export function createPatchFunction (backend{

  // ...

  function initComponent (vnode, insertedVnodeQueue{

    // ...

    vnode.elm = vnode.componentInstance.$el

    // ...

  }

  // ...

}

Copy the code

This function basically saves $EL on the App child instance to the elm property of the placeholder vNode

2.3 Mounting SubComponents

In fact, for component inserts, the insert method is called in createComponent:

// src/core/vdom/patch.js



export function createPatchFunction (backend{

  // ...

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm{

    let i = vnode.data

    if (isDef(i)) {

      if (isDef(i = i.hook) && isDef(i = i.init)) {

        // 2.1 Execute init hook. A subinstance has been created and mounted

        i(vnode, false /* hydrating */)

      }

      if (isDef(vnode.componentInstance)) {

        // 2.2 Setting the placeholder vnode elm

        initComponent(vnode, insertedVnodeQueue)

        // 2.3 Mounting subcomponents

        insert(parentElm, vnode.elm, refElm)



        // ...



        return true

      }

    }

  }

  // ...

}

Copy the code

The argument passed in when you call insert

  • parentElmIs:body
  • vnode.elmIn:initComponentTo complete the assignmentAppThe child component$el

This completes the DOM insertion of the component

conclusion

So here, a component of the VNode is how to create, initialize, render the process is also introduced.