This is the 15th day of my participation in Gwen Challenge

preface

When learning diff algorithm, I have seen that Vue creates real DOM nodes through createElm during patch. But we know that the virtual DOM is the focus of Vue throughout. Vue creates a virtual node using createElemnet. Create a virtual node using createElemnet.

Here is a piece of code we often write:

new Vue({
    router,
    store,
    render: h= > h(App)
}).$mount('#app');
Copy the code

The main function of this code is to create a Vue instance and mount it to the app. There is a key function called _render in the mountComponent method when $mount finally mounts the instance

_render

Before we look at createElement, let’s look at the _render function. This function is a private method of the Vue instance, its role is to render the Vue instance as a VNode, defined in SRC/core/instance/render. Js.

Vue.prototype._render = function () :VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
        vm.$scopedSlots = normalizeScopedSlots(
            _parentVnode.data.scopedSlots,
            vm.$slots,
            vm.$scopedSlots
        )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    
    // render self
    let vnode
    try{
        currentRenderingInstance = vm
        vnode = render.call(vm._renderProxy, vm.$createElement)
    }catch(e){
        ...
    }.finally{
        currentRenderingInstance = null
    }
    
        
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
        vnode = vnode[0]}// return empty vnode in case the render function errored out
    if(! (vnodeinstanceof VNode)) {
        vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
}
Copy the code

The _render function is mainly used to call the render function. The developer should not write the render function by hand, most of the template is written, and then compile the template into the render function in the corresponding mounted method.

Render function

Render all Vue components are converted to render functions.

Render :function(createElement){return createElment(App)} render:function(createElement){return createElment(App)} As for why use h, search, from Hyperscript

Context = context = context = context = context = context = context = context = context = context = context

Back to the vue.prototype. _render call, createElement is vm.$createElment

const { render, _parentVnode } = vm.$options
vnode = render.call(vm._renderProxy, vm.$createElement)
Copy the code

$createElement method defined in initRender function in SRC/core/instance/init. Js

export function initRender (vm: Component) {
  ...
  
  // internal version is used by render functions compiled from templates
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  
  ...
}
Copy the code

The vm.$createElment function is used for user-written render methods

We also define a vm._c for the template’s compiled render function.

In general, both methods call the createElement method.

_renderProxy defined in _init function definition in the SRC/core/instance/init. Js

if(process.env.NODE_ENV ! = ='production') {
    initProxy(vm)
} else {
    vm._renderProxy = vm
}
Copy the code

createElement

The createElment function actually calls _createElement

export function createElement (context: Component, tag: any, data: any, children: any, normalizationType: number,) :VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  return _createElement(context, tag, data, children, normalizationType)
}
Copy the code

Take a quick look at the _createElement method

export function _createElement (context: Component, tag? : string | Class<Component> |Function | Object, data? : VNodeData, children? : any, normalizationType? : number) :VNode | Array<VNode> {
  if (isDef(data) && isDef((data: any).__ob__)) {
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if(! tag) {// in case of component :is set to falsy value
    return createEmptyVNode()
  }
  
  ...
  
  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0= = ='function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined.undefined, context
      )
    } else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined.undefined, context
      )
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
}
Copy the code

_createElement takes five parameters: Vnode’s context, label, data, child node, and the canonical type of child node. NormalizationType is a number type, and different types of child nodes are specified differently, depending on whether the Render parameter is generated by template compilation or handwritten by the user.

You can see that _createElement does the following:

  • Standardization of the children
  • Depending on the tag type, call New Vnode to create a normal Vnode or call createComponet to create a component Vnode.
  • Finally, according to the different Vnode to do the corresponding processing, return the Vnode

The children diff algorithm is used to evaluate the children diff algorithm. The children diff algorithm is used to evaluate the children diff algorithm.

Specification child node

Specification different child node is mainly depending on the type of specification field calls the normalizeChildren and simpleNormalizeChildren respectively, these two functions defined in SRC/core/vdom/helpers/normalize – children. Js

 if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
Copy the code

simpleNormalizeChildren

The simpleNormalizeChildren method is called when the Render functional template is compiled. In general, the children of the render function generated are of type Vnode. But in a special case, functional Component returns an array instead of a root node, so concat is needed to flatten it to make sure it has only one layer of depth.

// The template compiler attempts to minimize the need for normalization by
// statically analyzing the template at compile time.
//
// For plain HTML markup, normalization can be completely skipped because the
// generated render function is guaranteed to return Array<VNode>. There are
// two cases where extra normalization is needed:
// 1. When the children contains components - because a functional component
// may return an Array instead of a single root. In this case, just a simple
// normalization is needed - if any child is an Array, we flatten the whole
// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
// because functional components already normalize their own children.

export function simpleNormalizeChildren (children: any) {
  for (let i = 0; i < children.length; i++) {
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children)
    }
  }
  return children
}
Copy the code

normalizeChildren

NormalizeChildren can be used in two scenarios: one is when a nested array is generated when template, slot, or V-for is compiled. The other is the children argument to come with the user-written render function.

When children is the base type written by the user, createTextVnode is called to create a text node. When children is an array, the normalizeArrayChildren method is called.

// 2. When the children contains constructs that always generated nested Arrays,
// e.g. <template>, <slot>, v-for, or when the children is provided by user
// with hand-written render functions / JSX. In such cases a full normalization
// is needed to cater to all possible types of children values.
export function normalizeChildren (children: any): ?Array<VNode> {
  return isPrimitive(children)
    ? [createTextVNode(children)]
    : Array.isArray(children)
      ? normalizeArrayChildren(children)
      : undefined
}
Copy the code

Take a look at the normalizeArrayChildren method:

function normalizeArrayChildren (children: any, nestedIndex? : string) :Array<VNode> {
  const res = []
  let i, c, lastIndex, last
  for (i = 0; i < children.length; i++) {
    c = children[i]
    if (isUndef(c) || typeof c === 'boolean') continue
    lastIndex = res.length - 1
    last = res[lastIndex]
    // nested
    if (Array.isArray(c)) {
      if (c.length > 0) {
        c = normalizeArrayChildren(c, `${nestedIndex || ' '}_${i}`)
        // merge adjacent text nodes
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)
          c.shift()
        }
        res.push.apply(res, c)
      }
    } else if (isPrimitive(c)) {
      if (isTextNode(last)) {
        // merge adjacent text nodes
        // this is necessary for SSR hydration because text nodes are
        // essentially merged when rendered to HTML strings
        res[lastIndex] = createTextVNode(last.text + c)
      } else if(c ! = =' ') {
        // convert primitive to vnode
        res.push(createTextVNode(c))
      }
    } else {
      if (isTextNode(c) && isTextNode(last)) {
        // merge adjacent text nodes
        res[lastIndex] = createTextVNode(last.text + c.text)
      } else {
        // default key for nested array children (likely generated by v-for)
        if (isTrue(children._isVList) &&
          isDef(c.tag) &&
          isUndef(c.key) &&
          isDef(nestedIndex)) {
          c.key = `__vlist${nestedIndex}_${i}__ `
        }
        res.push(c)
      }
    }
  }
  return res
}
Copy the code

We can add attributes to an array without changing its length:

Anyway, normalizeArrayChildren will iterate over children, assigning the current iterate item to variable C.

And then to determine if C is an array, if I’m recursing normalizeArrayChildren; If c is a basic type, call createTextVNode to convert it to VNode. If true, the children._isVList attribute is a nested array of lists. If true, nestedIndex is passed as the second parameter to update the key.

There is a last parameter in the code that holds the last node in the RES array. This parameter is used to determine whether the current node is a contiguous text node. If so, it is merged into a text node and assigned to res[lastIndex].