patch

CreateComponent creates a component VNode, vm._ update, vm._ patch_ to convert vNodes into real DOM nodes. This process has been covered in virtualDOM before, but for a normal VNode, let’s take a look at what makes a VNode different.

The patch process calls createElm to create element nodes. Review the implementation of createElm, which is defined in SRC /core/vdom/patch.js:

  function  createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      // This vnode was used in a previous render!
      // now it's used as a new node, overwriting its elm would cause
      // potential patch errors down the road when it's used as an insertion
      // reference node. Instead, we clone the node on-demand before creating
      // associated DOM element for it.vnode = ownerArray[index] = cloneVNode(vnode) } vnode.isRootInsert = ! nested// for transition enter check
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
      return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
      if(process.env.NODE_ENV ! = ='production') {
        if (data && data.pre) {
          creatingElmInVPre++
        }
        if (isUnknownElement(vnode, creatingElmInVPre)) {
          warn(
            'Unknown custom element: <' + tag + '> - did you ' +
            'register the component correctly? For recursive components, ' +
            'make sure to provide the "name" option.',
            vnode.context
          )
        }
      }

      vnode.elm = vnode.ns
        ? nodeOps.createElementNS(vnode.ns, tag)
        : nodeOps.createElement(tag, vnode)
      setScope(vnode)

      /* istanbul ignore if */
      if (__WEEX__) {
        // in Weex, the default insertion order is parent-first.
        // List items can be optimized to use children-first insertion
        // with append="tree".
        const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
        if(! appendAsTree) {if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
        createChildren(vnode, children, insertedVnodeQueue)
        if (appendAsTree) {
          if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue)
          }
          insert(parentElm, vnode.elm, refElm)
        }
      } else {
        createChildren(vnode, children, insertedVnodeQueue)
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        insert(parentElm, vnode.elm, refElm)
      }

      if(process.env.NODE_ENV ! = ='production' && data && data.pre) {
        creatingElmInVPre--
      }
    } 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

createComponent

Delete redundant code, keep the key logic, here will determine the createComponent (vnode, insertedVnodeQueue parenElm, refElm) if the return value is true the world over, So let’s look at the createComponent method implementation:

  function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        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 return the element and be done.
      if (isDef(vnode.componentInstance)) {
        initComponent(vnode, insertedVnodeQueue)
        insert(parentElm, vnode.elm, refElm)
        if (isTrue(isReactivated)) {
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true}}}Copy the code

The createComponent function first makes some judgments about vnode.data:

    let i = vnode.data
    if (isDef(i)) {
      if (isDef(i = i.hook) && isDef(i = i.init)) {
        i(vnode, false /* hydrating */)}}Copy the code

If vnode is a component vnode, then the condition is satisfied and I is the init hook function. The init function we saw earlier is defined in SRC /core/vdom/create-component.js

  init (vnode: VNodeWithData, hydrating: boolean): ? boolean {if( vnode.componentInstance && ! vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) {// kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
Copy the code

Init hook function is very simple, for a moment without considering the keepAlive, he is through createComponentInstanceForVnode create a Vue examples, and then call $mount method on child components, Look at the first createComponentInstanceFowVnode implementation:

export function createComponentInstanceForVnode (
  // we know it's MountedComponentVNode but flow doesn't
  vnode: any,
  // activeInstance in lifecycle state
  parent: any
) :Component {
  const options: InternalComponentOptions = {
    _isComponent: true._parentVnode: vnode,
    parent
  }
  // check inline-template render functions
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  return new vnode.componentOptions.Ctor(options)
}
Copy the code

CreateComponentInstanceForVnode function structure of an internal component parameters, and then execute the new vnode.com ponentOption. Ctor (options). This column ponentOptions vnode.com. Ctor (options) is the corresponding child component constructor, we analyzed the Sub, the equivalent of newSub (options), here are a few key to note a few points, _isComponent true means it’s a component, and parent means the currently active component instance.

So the child component instantiation is actually at this time, and he will also perform _ the init method, this process there are some different places before and need to jump out, the code in the SRC/core/instance/init. In js:

  Vue.prototype._init = function (options? :Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if(process.env.NODE_ENV ! = ='production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
Copy the code

_isComponent is true, so it goes to initInternalComponent. Look at this function briefly

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  const parentVnode = options._parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}
Copy the code

In this process, we mainly remember the following points: Opts._parentvnode = parentVnode, They are before we through createComponentInstanceForVNode function is introduced to the merger of several parameters in the $options.

Take a look at the last code executed by the _ init function:

if (vm.$options.el) {
    vm.$mount(vm.$options.el)
}
Copy the code

ComponentVNodeHooks init (); componentVNodeHooks init (); componentVNodeHooks init (); Init. ComponentVNodeHooks init. After instantiating _ init, execute child.mount. ComponentVNodeHooks init function. After instantiating init, execute child-mount (hydrating?). Vnode. Elm: undefined, hydrating). If the hydrating value is true, then the hydrating value is true. If the hydrating value is true, then the hydrating value is true. If the hydrating value is true, the hydrating value is true. He ends up calling the mountComponent method, which executes the vm._ render() method:

Vue.prototype._render = function () :Node{
    const vm:Component = this
    const { render, _parentVnode } = vm.$options
    
    vm.$vnode = _parentVnode
    let vnode 
    try {
        vnode = render.call(vm._renderProxy, vm.$createElement)
    }catch(e) {
        
    }
    vnode.parent = _parentNode
    return vnode 
}
Copy the code

$VNode is the parent VNode of the current component, and the render function generates the parent VNode of the current component. The parent VNode points to the parentVnode (vm.$VNode). They have a father-son relationship.

We know that after vm._ render is executed to generate the VNode, vm._ update is executed to render the VNode. To see what needs to be noted during component rendering, vm. _update is defined in the

SRC/core/instance/lifecycle. In 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
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if(! prevVnode) {// initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)}else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }
Copy the code

Vm. _ vnode = vnode. Vnode is rendered by the vm. _render() component. Vm. vnode and VM. vnode have a parent-child relationship. Expressed in code is vm. Vnode. Parent. = = = vm vnode relationship is a relationship of father and son, is expressed in the code vm. _vnode. Parent. = = = vm vnode relationship is a relationship of father and son, Vm.vnode. parent===vm.vnode

export let activeInstance: any = null
Vue.prototype._ update = function(vnode: VNode, hydrating? : boolean){
    / /...
    const preActiveInstance = activeInstance
    activeInstance = vm
    if(! preVnode) {// initial render
     vm.$el = vm._ patch _ (vm.$el, vnode, hydrating, false/*removeOnly*/)}else{
        vm.$el = vm._ patch _ (preVnode, vnode)
    }
    activeInstance = prevActiveInstance
}
Copy the code

Lifecycle is a global variable in the Lifecycle module defined as export let activeInstance: Any = null, and before we call createComponentInstanceForVnode method from lifecyle module, and passed as a parameter. Since Javascript is actually a single thread, the entire initialization of a Vue is a deep traversal process. In instantiating the child component, it needs to know what the instance of the Vue in the current context is and treat it as the parent Vue instance of the child component. Call initInternalComponent(VM, options), merge options, store parent in vm. options, in options, in options, Call the initLifecycle (vm) method back before mount:

export function initLifecycle (vm: Component) {
    const options = vm.$options
    // locate first non-abstract parent
    let parent = options.parent
    if(parent && ! options.abstract) {while (parent.$options.abstract && parent.$parent) {
            parent = parent.$parent
        }
        parent.$children.push(vm)
    }
    
    vm.$parent = parent
    // ...
}
Copy the code

Parent is used to hold the parent instance of the current VM, and parent is used to hold the parent instance of the current VM through parent. Parent is used to hold the parent instance of the current VM through parent.

Let’s store the current VM in the parent instance’s $children.

During vm_update, the parent relationship is preserved via vm.$parent.

So back to _ update, the final call to _ patch __ renders VNode.

vm.$el = vm.__patch__ (vm.$el,vnode,hydrating,false/*removeonly*/)
function patch (oldVnode,vnode,hydrating,removeOnly) {
    // ...
    let isInitialPath = false
    const insertedVnodeQueue = []
    
    if (isUndef(oldVnode)){
        //empty mount (likely as component),create new root element
        isInitialPath = true
        createElm(vnode, insertedVnodeQueue)
    }else{
        / /...
    }
    / /...
}
Copy the code

This is where we started with createElm, the function that renders to the DOM. Note that we only pass two arguments, so parentElm is undefined. Let’s look at his definition again:

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArrary, index){
        // ...
        if(createComponent(vnode, insertedVnodeQueue, parentElm, refElm)){
            return 
        }
        const  data = vnode.data
        const children = vnode.children
        const tag = vnode.tag
        if(isDeftag){
           / /...
            vnode.elm = vnode.ns? node.createElementNS(vnode.ns, tag):
            nodeOps.createElement(tag, vnode)
           setScope(vnode)
            
            /* istanbul ignore if*/
            if(__week__){
               // ... 
            }else{
                createChildren(vnode, children, insertedVnodeQueue)
                if(isDef(data)){
                   insert(parentElm, vnode.elm, refElm)
                }
                // ...
            }else if (isTrue(vnode.isComment)) {
                vnode.elm = nodeOps.createComment(vnode.text)
                insert(parentElm, vnode.elm, refElm)
            }
         }
}
Copy the code

Note that the vnode we pass in here is the component-rendered vnode (vm._vnode). If the root node is a normal element, then vm._vnode is also a normal vnode. Here the createComponent (vnode, insertedVnodeQueue parentElm, refElm) of the return value is false. Create a parent node placeholder and recursively call createElm through all the child VNodes. If a child VNode is a VNode of a component, repeat the process, so that the entire tree can be built recursively.

Since parentElm passed in at this time is empty, there is logic in createComponent for the insertion of the component:

function createComponent (vnode, insertedVnodeQueue, parentElm, refElm){
    let i = vnode.data
    if(isDef(i)){
       // ...
        if(isDef(i = i.hook) && isDef(i = i.init)){
           i(vnode, false/*hydrating*/)}// ...
        if(isDef(vnode.componentInstance)){
            initComponent(vnode, insertedVnodeQueue)
            insert(parentElm, vnode.elm, refELm)
            if(isTure(isReactivated)){
               reactivateComponent(vnode, insertedVnodeQueue, parentElm, refELm)
            }
       return true}}}Copy the code

After completing the whole patch process, execute insert(parentElm, vnode.elm, refElm) to insert the DOM of the component. If a child component is created during the patch process, the DOM insertion sequence is child before parent.

conclusion

So to this, a component of the VNode is how to create, initialization, rendering process is also introduced. After a general understanding of the implementation of the component, we will introduce some of the details, we know that writing a component is actually writing a JavaScript object, the description of the object is a variety of configuration, Merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge options = merge