(juejin. Cn/post / 705288… “Juejin. Cn/post / 705288…

Previously on & Background

We have simplified createPatchFunction and the patch function it returns, leaving only the code that can express the initial rendering process as follows:

Patch calls createElm to turn the VNode tree into a real DOM tree and insert it into the body. CreateElm creates native HTML elements and custom components.

Since rendering a custom component is a big job, this article will talk about the rendering process of custom components.

Note: The rendering of this custom component is not independent, it is a branch of the initial rendering process. Why do you say so? Take this template as an example:

, first render from the div virtual node with id app, and then render some-com when it renders its children. CreateElem is now ready to handle the custom components. So the initial rendering of the root instance does not conflict with the initial rendering of the component.

Second, the createElm

SRC /core/vdom/patch.js -> function createPatchFunction internal method

Method parameters:

  1. vnode: virtual node instance object, called earlier on the first renderingvm._render()Get the entire virtual DOM tree
  2. insertedVnodeQueue, node queue to be inserted
  3. parentElm, the parent node, when thevnodeBecome a realdomInsert into the parent node
  4. refElmThe: reference element is the sibling of div#app, and if it has a value, the DOM rendered by vnode is inserted in front of it
  5. nested, whether nested
  6. owernArray, owner array
  7. indexIndex,

Method creates native HTML elements and custom components.

function createElm (vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { vnode.isRootInsert = ! nested// Here's the thing:
  // This createComponent is responsible for handling cases where vNode is a custom component
  // If vNode is a normal element, createComponent returns false
  if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
    // If vNode is a custom component, createComponent returns true and terminates
    return
  }

  // VNode is a normal element
  // Get the data object
  const data = vnode.data
  
  // Get the list of child nodes
  const children = vnode.children
  
  // Label name of a vnode
  const tag = vnode.tag
  if (isDef(tag)) {
  

    // Create a new node and mount it to the vNode object.
    Vnode. elm is a real DOM element
    vnode.elm = vnode.ns // ns is the namespace, ignore it
      ? nodeOps.createElementNS(vnode.ns, tag)
      : nodeOps.createElement(tag, vnode) // Let's study the case
   

    if (__WEEX__) {
   
    } else {
      // Recursively create all child nodes (common elements, components)
      createChildren(vnode, children, insertedVnodeQueue)
      if (isDef(data)) {
        / / call createHooks
        invokeCreateHooks(vnode, insertedVnodeQueue)
      }

      // Insert the node into the parent node for the first rendering, which is a crucial step,
      // vnode.elm is the real element created, and there is a whole DOM tree containing all the template content,
      ParentElm is the body element
      // Insert the DOM element into the body to render
      insert(parentElm, vnode.elm, refElm)
    }
  } else if (isTrue(vnode.isComment)) {
    // The vnode.tag attribute does not exist, that is, it is not an element or custom component
    Create a comment node and insert the parent node
    vnode.elm = nodeOps.createComment(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  } else {
    // Not a comment, not an element, just text processing
    // Text node, create text node and insert parent node
    vnode.elm = nodeOps.createTextNode(vnode.text)
    insert(parentElm, vnode.elm, refElm)
  }
}
Copy the code

2.1 the createComponent

SRC /core/vdom/patch.js -> function createPatchFunction internal method

Method parameters:

  1. vnode: Node object
  2. insertedVnodeQueue, list of nodes to be inserted
  3. parentElmThe parent element
  4. refElm.refWith reference to the element

Methods:

  1. ifvnodeIs a component, then executesinitHook to create and mount the component instance
  2. Then execute the individual module’s for the componentcreatehook
  3. If the component iskeep-alivePackage, then activate the component
  4. Returns if it is a custom componenttrueIf it is a normal HTML element and nothing is returnedundefined
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  // Get the vnode.data object
  let i = vnode.data
  if (isDef(i)) {
    // Check if the component instance already exists && is wrapped by 
      
    // Components wrapped by keep-alive are activated and deactivated. Normal components need to be created and destroyed
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive

    // Execute the vnode.data.hook. Init hook,
    // This thing is generated with installComponentHooks when a VNode is generated
    // If the component is wrapped by keep-alive:
    // Execute the prepatch hook to update the oldVnode properties with the vNode properties
    // If the component is not wrapped by keep-alive or rendered for the first time, initialize the component and enter the mount phase
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      i(vnode, false /* hydrating */)}// After the data.hook. Init hook is called, if the vNode is a child component,
    // At this point the child component instances should have been generated and mounted
    // Set vnode.elm for the child component
    if (isDef(vnode.componentInstance)) {
      // If a vNode is a child component,
      // A component instance is created and mounted after the data.hook.init hook is called
      // Execute the create hook for each module to the component
      initComponent(vnode, insertedVnodeQueue)

      // Insert the component's DOM node into the parent node
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        // Activate the component if it is wrapped by keep-alive
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true}}}Copy the code

2.1.1 data. The hook. The init

Data is an object derived from the inline attributes and instructions of elements in the template. When creating a custom component VNode, the hook attribute will be added to vNode. data, which contains four hook methods: Init/prepatch/insert/destroy, this process is roughly process code sample is as follows:

export function createComponent () {
  installComponentHooks(data);
}

// installComponentHooks
function installComponentHooks (data: VNodeData) {
  // The data.hook object is initialized
  const hooks = data.hook || (data.hook = {})

  // Walk through the hooksToMerge array,
  // hooksToMerge = Object.keys(componentVnodeHooks)
  // hooksToMerge = ['init', 'prepatch', 'insert', 'destroy']
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
   
  }
}
Copy the code

The hook methods are in the componentVnodeHooks object. They work like this:

  1. To deal withvnode.componentInstanceAlready exists and the component iskeep-alivePackage, then call directlyhook.prepatchEnter thepatchBecause thekeep-aliveThe component is not destroyed. There is no need to recreate the component instancepatchUpdate render;
  2. Another scenario involves creating component instances and mounting them tovnode.componentInstanceOn. When creating a component instanceConstructor of the new componentAnd thisThe constructorIs in thecreateComponentThrough the componentThe options objectandVue.optionsMerge, and then inheritVueThe resulting subclass:function VueComponent;
  3. After obtaining the instance, manually invoke the child component’s$mountMethod, so that the child component into the mount phase; This is where it gets interesting. Subcomponents$mountThe child component template is then compiled (parse+generate)Child componentstheRender functionAnd then createChild componentstheRender the watcher,VNodeAnd then callSubcomponents. _update (). You can see that this is a recursive process, and if there are children, the cycle continues until all the components are mounted to the corresponding parent node.
const componentVNodeHooks = {
  / / initialization
  init (vnode: VNodeWithData, hydrating: boolean): ? boolean {if (
      vnode.componentInstance && // The component instance already exists! vnode.componentInstance._isDestroyed &&// The component instance was not destroyed
      vnode.data.keepAlive // The component is in a keep-alive package
    ) {
      
      // The init process of a keep-alive component is prepatch
      const mountedNode: any = vnode
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      // Common component creation process:
      // Execute this, and you get vnode.componentInstance,
      // That is, the instance created by the component constructor
      // The constructor of this instance is a subclass extending Vue, inheriting Vue's capabilities
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // Execute the component's $mount method to manually mount it
      // Enter the mount phase, the next step is to get the render function from the compiler,
      // Then create render watcher then go to components mount, patch
      // This path continues until the component is mounted to the parent node
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

  // Update the VNode. Configure the updated VNode with the new VNode
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
  
  },

  // Executes the component's Mounted lifecycle hook
  insert (vnode: MountedComponentVNode) {
   
  },

  // Destroy component:
  destroy (vnode: MountedComponentVNode) {
    
  }
}
Copy the code

2.1.2 data. Hook. Prepatch

Prepatch is more about expressing the rendering process of diff + patch after the change of responsive data, which is not expanded for the time being

const componentVNodeHooks = {
  / / initialization
  init (vnode: VNodeWithData, hydrating: boolean): ? boolean {},// Update VNode, update old VNode with new VNode configuration
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    // New VNode, with the new VNode to configure the various properties of the updated VNode
    const options = vnode.componentOptions
    // Component instance of the old VNode component
    const child = vnode.componentInstance = oldVnode.componentInstance

    Update various attributes on child with attributes on vNode
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },


  insert (vnode: MountedComponentVNode) {},
  destroy (vnode: MountedComponentVNode) {}
}
Copy the code

2.1.3 createComponentInstanceForVnode

Methods location: SRC/core/vdom/create – component. Js – > function createComponentInstanceForVnode

Method parameters:

  1. vnode, virtual node
  2. parentThe parent element

Method Function: to

export function createComponentInstanceForVnode (
  vnode: any,
  parent: any // The currently active parent instance, such as 
        , is the root instance
) :Component {
  const options: InternalComponentOptions = {
    _isComponent: true.// Identifies the current instance as a component
    _parentVnode: vnode, // The parent node of the current component is vnode
    parent
  }
  // Check the render function of the inline template, if it is an inline template,
  // Replace the render function with the inline template render function
  // Inline template, see vue official documentation
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  / / vnode.com ponentOptions Ctor is generated when vnode extension Vue of subclasses
  // Each custom component has its own subclass constructor
  return new vnode.componentOptions.Ctor(options)
}
Copy the code

2.1.4 initComponent

Initialize the component. This is done in the invokeCreateHooks method. Note that the create is not a component’s Created lifecycle hook. But attributes, style, and the name of the class, instructions, ref (attrs/stle/klass/directives/ref) cycle method, These methods are in the Modules option passed in when createPatchFunction({nodeOps, modules}) is executed;

In the official document of Vue, there is a periodic function to introduce instructions, which is called hook;

function initComponent (vnode, insertedVnodeQueue) {
  if (isDef(vnode.data.pendingInsert)) {
    insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
    vnode.data.pendingInsert = null
  }
  vnode.elm = vnode.componentInstance.$el
  if (isPatchable(vnode)) {
    // For the first rendering, call the create hook method of modules;
    // Note that this is not a Created life cycle for the component, but rather for the ATTr, Klass (class name), style, and directives properties
    // For maintenance, here is an example of the directive directives directives hook function:
    // https://cn.vuejs.org/v2/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0
    invokeCreateHooks(vnode, insertedVnodeQueue)
    setScope(vnode)
  } else {
    // empty component root.
    // skip all element-related modules except for ref (#3455)
    registerRef(vnode)
    // make sure to invoke the insert hook
    insertedVnodeQueue.push(vnode)
  }
}
Copy the code

2.2 nodeOps createElement method

For example, nodeOps encapsulates the browser’S DOM API. CreateElement is the method to create an element, which is a real DOM element.

// Create an element node with the tag tagName
export function createElement (tagName: string, vnode: VNode) :Element {
  // Create the element node
  const elm = document.createElement(tagName)
  if(tagName ! = ='select') {
    return elm
  }
  // If the select element, set the multiple attribute for it

  if(vnode.data && vnode.data.attrs && vnode.data.attrs.multiple ! = =undefined) {
    elm.setAttribute('multiple'.'multiple')}return elm
}
Copy the code

2.3 createChildren

SRC /core/vdom/patch.js -> function createPatchFunction internal method

Method parameters:

  1. vnode, virtual node list
  2. children.vnode.childrenList, that is, the child node list of the current virtual node.
  3. insertedVnodeQueue, the list of nodes to be inserted

Create an element from vnode.children recursively and insert it into the parent element (vnode.elm).

// Create all child nodes and insert the child nodes into the parent node to form a DOM tree
function createChildren (vnode, children, insertedVnodeQueue) {
  if (Array.isArray(children)) {
    // children is an array that identifies a set of nodes
    if(process.env.NODE_ENV ! = ='production') {
      // Check whether the key of this group of nodes is duplicated
      checkDuplicateKeys(children)
    }

    // Iterate through the list of child nodes, recursively create these nodes by calling createElm,
    // Then insert the parent node to form a DOM tree
    for (let i = 0; i < children.length; ++i) {
      createElm(children[i], insertedVnodeQueue, vnode.elm, null.true, children, i)
    }
  } else if (isPrimitive(vnode.text)) {
    // Text node, create text node and insert parent node
    nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
  }
}
Copy the code

2.4 invokeCreateHooks

Call the CREATE methods of each module, such as ATTRs, style, and directives, and then execute the mounted lifecycle methods of the component. These modules are modules passed in when createPatchFunction({nodeOps, modules});

function invokeCreateHooks (vnode, insertedVnodeQueue) {
  for (let i = 0; i < cbs.create.length; ++i) {
    cbs.create[i](emptyNode, vnode)
  }
  i = vnode.data.hook // Reuse variable
  if (isDef(i)) {
    if (isDef(i.create)) i.create(emptyNode, vnode)

    // Invoke the component's data.hook. Insert hook to execute the component's Mounted lifecycle method
    if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
  }
}
Copy the code

Insert hook is a vNode.data.hook that installs Componenthooks on data.hook when creating a VNode

Against 2.4.1 data. The hook. The insert

const componentVNodeHooks = {
 
  init (vnode: VNodeWithData, hydrating: boolean): ? boolean {},// Update the VNode. Configure the updated VNode with the new VNode
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {},

  // Executes the component's Mounted lifecycle hook
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode

    // If the component is not mounted, the component's Mounted lifecycle hook is called
    if(! componentInstance._isMounted) { componentInstance._isMounted =true
      callHook(componentInstance, 'mounted')}// Handle the keep-alive exception....
  },

  // Destroy component:
  destroy (vnode: MountedComponentVNode) {}
}
Copy the code

2.5 insert

SRC /core/vdom/patch.js -> function createPatchFunction internal method

Method parameters:

  1. parentThe parent node,
  2. elmThe element to be inserted into the parent node queue
  3. ref, reference node, yeselmThe younger nodes ensure the order of insertion of the later nodes

Method: Insert ELM into the parent child node queue. After performing this step, the VNode becomes a real DOM.

function insert (parent, elm, ref) {
  if (isDef(parent)) {
    if (isDef(ref)) {
      if (nodeOps.parentNode(ref) === parent) {
        // is inserted before the ref reference node, so ref is the younger node of elm
        nodeOps.insertBefore(parent, elm, ref)
      }
    } else {
      // Appends to the end of the child node of parent
      nodeOps.appendChild(parent, elm)
    }
  }
}
Copy the code

Third, summary

3.1 Summary

This article discussed in detail the logic of the createElm method, which creates real elements based on VNode and contains two scenarios:

  1. If a VNode is a custom component, the createComponent method is called. Data.hook. init (Vue. Prototype. _init) is called internally to instantiate the child component. Then call $mount of the subcomponent to start compiling and mounting the subcomponent, and complete the rendering of the subcomponent; In the process of rendering sub-components, the initial rendering logic of patch function about sub-components will be triggered.

  2. If it is a normal element, the native HTML element is created with nodeOps. CreateElement and the processing of its children is a recursive call to the createElm method.

  3. At the end of the createElm method, the vNode. Elm is inserted into parentElm, where parentElm is the body element.

3.2 Mounting Stage Summary

This was followed by a series of steps to remove placeholder nodes until vue.prototype.$mount implemented the stack and Vue’s initial rendering was completed.

$mount = new Vue; $mount = new Vue; $mount = new Vue;

New Vue() -> Vue.prototype._init() -> Vue. Prototype.$mount() -> compileToFunctions(template...) Mount () -> mountComponent() -> new Watcher(updateComponent) -> updateComponent -> Vue.prototype._render() -> vue.prototype. _update() -> vue.prototype. __patch__() -> createPatchFunction Patch () -> createElm() -> insert(parentElm, vnode.elm)Copy the code