CreateElement is used to create a VNode

1. About the vNode

Since createElement returns a VNode, it’s important to understand some of the concepts of a VNode.

  • aboutvnodeThe constructor can be seenhere
  • vnodeisjsObject. Avoid frequent DOM manipulation to improve performance.
  • The component’svnodeThere are two:
    • A placeholdervnode:vm.$vnodeOnly component instances
    • Apply colours to a drawingvnode:vm._vnodeYou can use thisVNodeIt maps directly to realityDOM
    • They are father-son relationships:vm._vnode.parent = vm.$vnode
    • The concepts are introduced here, and will be covered in the component Patch section

2. createElement

CreateElement returns a VNode

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



// Constant definition

const SIMPLE_NORMALIZE = 1

const ALWAYS_NORMALIZE = 2



export function createElement (

  context: Component.

  tag: any.

  data: any.

  children: any.

  normalizationType: any.

  alwaysNormalize: boolean

): VNode | Array<VNode> {

  // Parameter overload, which means that the data argument is actually optional

  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

CreateElement is basically a wrapper around _createElement that does two things:

  • If the third argument is an array or primitive type (excluding null and undefined), then the argument is overloaded.
  • judgealwaysNormalizeWhether it istruethennormalizationType = ALWAYS_NORMALIZE
// The internal function of the render function call generated by template compilation
vm._c = (a, b, c, d) = > createElement(vm, a, b, c, d, false)
// to be used by user-written render functions
vm.$createElement = (a, b, c, d) = > createElement(vm, a, b, c, d, true)
Copy the code

Note:

  • Written manually by the userrenderFunction,normalizationTypeIt must be constantALWAYS_NORMALIZE
  • Generated by compilationrenderFunction,normalizationTypeAccording to the callvm._cDepending on the specific value passed in

The main purpose of normalizationType is to distinguish how children should be regulated, which we’ll discuss below

The next step is to execute the actual handler, _createElement

3. _createElement

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

  // There are some edge cases that need not be concerned for the moment:

  // 1. The passed data parameter cannot be the observed data

  // 2. Dynamic component processing

  // 3. A warning is thrown if the key value is not a primitive type

  // 4. support single function children as default scoped slot

  

  // Core logic 1: normalize chidlren

  if (normalizationType === ALWAYS_NORMALIZE) {

    children = normalizeChildren(children)

  } else if (normalizationType === SIMPLE_NORMALIZE) {

    children = simpleNormalizeChildren(children)

  }

  

  // Core logic 2: Create a Vnode

  let vnode, ns

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

    let Ctor

    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)

    // Whether to keep tags native to HTML

    if (config.isReservedTag(tag)) {

      if(process.env.NODE_ENV ! = ='production' && isDef(data) && isDef(data.nativeOn)) {

        warn(

          `The .native modifier for v-on is only valid on components but it was used on <${tag}>. `.

          context

        )

      }

      vnode = new VNode(

        config.parsePlatformTagName(tag), data, children,

        undefined.undefined, context

      )

    

    // Is the registered component name

    } else if((! data || ! data.pre) && isDef(Ctor = resolveAsset(context.$options,'components', tag))) {

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



    // Unknown or unlisted namespace elements

    // and so on are checked at runtime, because a namespace may be assigned to a child when its parent normalizes it

    } else {

      vnode = new VNode(

        tag, data, children,

        undefined.undefined, context

      )

    }

  } else {

    // direct component options / constructor

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

  }

  

  / / return vnode

  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

3.1 Standardized children

  • ifnormalizationTypeIs a constantALWAYS_NORMALIZE, that is, user-writtenrenderDelta function, so let’s use delta functionnormalizeChildrenTo normalize the child nodes
  • ifnormalizationTypeIs a constantSIMPLE_NORMALIZE, then usesimpleNormalizeChildren


Why we need normalizationchildren?

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. There are two cases where extra normalization is needed:

Translation:

The template compiler tries to avoid normalization by statically analyzing the template at compile time.

For string templates with pure HTML tags, it’s possible to skip the normalization altogether because it ensures that the generated render function returns Array

.

But there are two cases that require additional normalization:

3.1.1 simpleNormalizeChildren

// src/core/vdom/helpers/normalize-children.js



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
  • childrenCan contain functional components. The functional component returns an array instead of a root node. ifchildrenWe have an array, and we need to flatten it, so that’s going to bechildrenConvert to a one-dimensional array.
  • useArray.prototype.concatitfaltten. Since a functional component has normalized its own child levels, the depth is guaranteed to be only 1 level.

3.1.2 normalizeChildren

// src/core/vdom/helpers/normalize-children.js



export function normalizeChildren (children: any): ?Array<VNode{

  return isPrimitive(children)

    ? [createTextVNode(children)]

    : Array.isArray(children)

      ? normalizeArrayChildren(children)

      : undefined

}

Copy the code

The following situations require a call to normalizeChildren to normalize

  • handwrittenrenderFunction orJSXWhen,childrenAllows primitive types to be written to create a single simple text node, which is calledcreateTextVNodeTo create a text nodeVNode
  • When compiling<template>,slot,v-forGenerates a nested array, which is callednormalizeArrayChildrenmethods

Take a look at normalizeArrayChildren below:

// src/core/vdom/helpers/normalize-children.js



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]

    // If it is a nested array

    if (Array.isArray(c)) {

      if (c.length > 0) {

        // recursive processing

        c = normalizeArrayChildren(c, `${nestedIndex || ' '}_${i}`)

        // If there are two consecutive text nodes, they are merged into a single text node

        if (isTextNode(c[0]) && isTextNode(last)) {

          res[lastIndex] = createTextVNode(last.text + (c[0] :any).text)

          c.shift()

        }

        res.push.apply(res, c)

      }

    // If it is a primitive type

    } else if (isPrimitive(c)) {

      if (isTextNode(last)) {

        // If there are two consecutive text nodes, they are merged into a single text node

        // This is necessary for SSR tells, because text nodes are essentially merged when rendered to HTML

        res[lastIndex] = createTextVNode(last.text + c)

      } else if(c ! = =' ') {

        // convert primitive to vnode

        res.push(createTextVNode(c))

      }

    // It is already a VNode

    } else {

      if (isTextNode(c) && isTextNode(last)) {

        // If there are two consecutive text nodes, they are merged into a single text node

        res[lastIndex] = createTextVNode(last.text + c.text)

      } else {

        // If children is a list and nestedIndex exists, update the key (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

NormalizeArrayChildren receives 2 parameters:

  • childrenRepresents the child node to be normalized
  • nestedIndexRepresents nested indexes because of a singlechildIt could be an array type

The main logic of normalizeArrayChildren is to traverse children, get a single node C, and then determine the type of C

  • An array of: recursive callnormalizeArrayChildren
  • The base typeThrough:createTextVNodeMethod convert toVNodetype
  • Vnode typeIf:childrenIs av-forList, then according tonestedIndexTo update itskey.
    • Generated during compilationrenderIn the functionvm._l(...)Will be calledrenderListFunction, which will mount one_isVListThe variable is used to indicate that this isv-forThe list of
    • whenv-forA normal HTML tag is automatically processedkey.v-forA component when a componentkeyIf notundefined
// src/core/instance/render-helpers/render-list.js



export function renderList (

  val: any.

  render: (

    val: any.

    keyOrIndex: string | number.

index? :number

  ) => VNode

): ?Array<VNode
{

  // ...

  (ret: any)._isVList = true

  return ret

}

Copy the code

In the traversal process, the following is done for all three cases: if there are two consecutive text nodes, they are merged into a single text node.

After normalization of children, children becomes an Array of type VNode

3.2 create vnode

When tag is a string:

  • callconfig.isReservedTagFunction judgment iftagIf it is a built-in tag, create a corresponding oneVNodeObject.
  • iftagIf it is a registered component namecreateComponentFunction.
  • tagIs an unknown tag name, which will be created directly by the tag namevnode, and then wait until runtime to check, because its parent may assign namespaces to its children when they normalize.

When tag is not a string:

  • throughcreateComponentOf the component typeVNodeWe’ll talk about that later

conclusion

Each VNode has children. Each child element is a VNode. This creates a VNode Tree. It describes our DOM Tree very well.

Vue source code 1.3: $mount

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}
Copy the code

Now that we know how vm._render creates a VNode, the next step is to render the VNode into a real DOM, using vm._update, which we’ll look at in the next chapter.