From the previous article “Parsing the Compile Process”, we learned that the HTML template is compiled to produce a render function. The main function of the render function is to generate vNodes. There are two main tasks involved in rendering a VUE: creating a VNode and creating a DOM node. As a continuation of the previous article, this article focuses on the implementation of the rendering process.

Generate vnode

It is well known that a complex page will contain a large number of DOM nodes. In order to update these DOM nodes efficiently, Vue designed the concept of virtual DOM. A virtual DOM is a description of real DOM node information. In vUE, each DOM node has a virtual DOM node corresponding to it. This virtual DOM node is also called a Vnode, and the entire VNode tree composed of vNodes is the virtual DOM.Here’s an example of how to create a VNode. Suppose the following template is given:

<div id="app" @click="add">{{count}}</div>
Copy the code

After compiling, we get the following render function:

function render(
) {
  with(this){
    return _c('div', {attrs: {"id":"app"}, on: {"click":add}}, [_v(_s(count))])
  }
}

Copy the code

The _c function is added to the vue instance during initialization of the Render environment to create the global instance method of vNode. It can be called directly from a VUE instance, mainly to create methods for vNodes used inside a VUE. The underlying implementation is the same as vue’s $createElement API, which calls the internal _createElement method. _createElement core code is as follows:

function _createElement(context, tag, data, children, normalizationType){
    / /... Omit other code
    let vnode = new VNode(tag, data, children, undefined.undefined, context)
    / /... Omit other code
    return vnode
}
Copy the code

A Vnode contains the following information:

{
    // Element tags, such as div
    tag,
    For example, {attrs: {id: 'app'}}
    data,
    // VNode child node array
    children,
    // The text that the element contains
    text,
    // Corresponding DOM node
    elm,
    // The corresponding vue instance
    context,
    // Parent node vnode
    parent,
    / /... Omit the other
}
Copy the code

The _v method is also a VUE instance method and is used internally to create a text vNode. In this case, {{count}} is a text node, so _v is used to create a text VNode. However, both text vNodes and non-text VNodes are instances of vNode objects. The difference is that text vNodes do not have tags and children.

// Create a text VNode
function createTextVNode (val) {
  return new VNode(undefined.undefined.undefined.String(val))
}
Copy the code

The _s method is also an instance method of vue, internally used to return the received arguments as strings, using the Object.tostring () conversion for strings and values, or json.stringify () conversion if an Object is received.

function toString (val){
  return val == null
    ? ' '
    : Array.isArray(val) || (isPlainObject(val) && val.toString === Object.prototype.toString)
      ? JSON.stringify(val, null.2)
      : String(val)
}
Copy the code

A Vnode connects the parent node to the child node through parent and children to form a VNode tree.

Creating a DOM Node

With vNode, vUE also needs to create DOM nodes based on vNodes. If it is the first rendering, then vue follows the creation logic. If a data update causes a re-rendering, vUE follows the logic of the update.

For the first time to render

Since this is the first rendering, there are no previous old VNodes, so no comparison is required. Vue directly calls the createElm method to create the DOM element. The creation procedure is as follows:

  1. Start by creating a DOM element for the vNode.
  2. If the VNode has child nodes, create DOM elements one by one for the child nodes and insert the child DOM elements into the VNode’s DOM elements.
  3. callsetAttributeAdd attributes to vNode DOM elements.
  4. Insert vNode’s DOM element onto its parent element.

The main code for the createElm method is as follows:

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
    if (isDef(vnode.elm) && isDef(ownerArray)) {
      vnode = ownerArray[index] = cloneVNode(vnode)
    }
    
    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    // Common elements
    if (isDef(tag)) {
        // 1. Create a DOM node for vNode
        vnode.elm = nodeOps.createElement(tag, vnode)
        // Set the scope of the CSS
        setScope(vnode)
      
        // 2. Create a DOM node for the child node of the vnode and insert it into the CORRESPONDING DOM node of the vnode
        createChildren(vnode, children, insertedVnodeQueue)
        // 3. Update the attributes of DOM elements, such as class, style, event, etc
        if (isDef(data)) {
          invokeCreateHooks(vnode, insertedVnodeQueue)
        }
        // 4. Insert the vNode DOM node into the parent element (the parent element of the root node is body)
        insert(parentElm, vnode.elm, refElm)
    } else if (isTrue(vnode.isComment)) {
      If vNode is an annotation, create the annotated DOM and insert it into the parent element
      vnode.elm = nodeOps.createComment(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    } else {
      // If vNode is text, create the text DOM and insert it under the parent element
      vnode.elm = nodeOps.createTextNode(vnode.text)
      insert(parentElm, vnode.elm, refElm)
    }
    
    / /... Omit other code
}
Copy the code

As we all know, data data objects hold attributes related to DOM elements, such as ID, class, style, event, ref, and so on. Vue has special modules for adding, updating, or removing these attributes to DOM elements. Therefore, we can see in the code that if a vnode has a data object, Vue calls invokeCreateHooks to handle the attributes in data using the corresponding processing module.

To render

Vue maximizes reuse of created DOM elements if it is not a first rendering but a re-rendering triggered by data changes. The premise of reuse is to compare old and new VNodes, find out what needs to be updated, and then replace it as little as possible. This is also the core purpose of VUE design vNode.

  function patchVnode (oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly) {
    // If the old and new vNodes have not changed, no update is required.
    if (oldVnode === vnode) {
      return
    }

    const elm = vnode.elm = oldVnode.elm

    let i
    const data = vnode.data
    const oldCh = oldVnode.children
    const ch = vnode.children
    // 1. Update the DOM element attributes
    if (isDef(data) && isPatchable(vnode)) {
      // Re-execute all instructions and modules of the vue instance
      for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
    }
    
    // 2. Update child elements
    // If vNode is not a text node, the child element is updated.
    if (isUndef(vnode.text)) {
      // If both the new vNode and the old vnode have children
      if (isDef(oldCh) && isDef(ch)) {
        // If the child vNodes of the old and new vNodes are different, the child element is updated
        if(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if (isDef(ch)) {
        // If the new vNode has children and the old vnode has none, then the textContent of the DOM element needs to be set to empty
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
        // Create a DOM element for the children of the new vNode
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        // If the old vnode has children and the new vnode has no children. Then you need to remove the child VNodes from the old vNode
        removeVnodes(oldCh, 0, oldCh.length - 1)}else if (isDef(oldVnode.text)) {
        // if both the old and new vnodes have no children and the new vnode has no textContent but the old vnode has textContent, set the textContent of the dom element to empty
        nodeOps.setTextContent(elm, ' ')}}else if(oldVnode.text ! == vnode.text) {// The old and new vNodes are text nodes, and the text of the two vNodes is different, then the DOM textContent is updated.
        nodeOps.setTextContent(elm, vnode.text)
    }
    / /... Omit other code
  }
Copy the code

As you can see from the source code, when the old and new VNodes are exactly equal, vue does not re-render the node and skips it.

If the new VNode changes, the VUE updates the DOM element by following these steps:

  1. Update the attributes of the DOM element. This was mentioned a little bit in the first render section. Several attribute processing modules are implemented in VUE specifically for creating and updating DOM element attributes. Basically all of these modules are implementedcreate,updateThese two handlers.createResponsible for creating DOM element attributes,updateResponsible for updating DOM element attributes.cbs.update[i](oldVnode, vnode)Call each of these modules one by oneupdateMethod to update the changed DOM element attributes.
  2. Update the child elements of the DOM element. There are several cases for updating DOM child elements
    1. If the children of the old and new vnodes are text nodes and the textContent is different, the DOM element’s textContent property value is updated.
    2. If the children of the old and new VNodes are non-text nodes, the call is requiredupdateChildrenRecursively update child nodes.
    3. If the children of the new vnode are non-text nodes and the children of the old vnode are text nodes, the text of the DOM element needs to be cleared and the DOM element created by the child vNode is inserted onto the DOM element of its parent node.
    4. If the child of the new vnode does not exist, but the child of the old vnode doesremoveVnodeDelete the DOM element corresponding to the child node of the old VNode.
    5. If the children of the old VNode are text nodes and the children of the new vnode do not exist, the text of the old DOM element is cleared.

summary

A lot of DOM manipulation can take a toll on browser performance. Vue regenerates vNode nodes after each data change. Find the smallest subset of DOM elements to operate on by comparing old and new VNodes. According to the change point, DOM element attributes and DOM child nodes are updated. This design approach greatly reduces the number of DOM operations.

Stay tuned for the next vUE component mechanism.

Pay attention to our