Vue ultimately updates views through the updateComponent method, which calls vm._update(vm._render(), hydrating). $options. Render; Let’s take a look at what the update and render functions do.

render

When we use vue-CLI, main.js usually has this code

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

Render is a function that takes h, h is also a function, so what is h? H is essentially the createElement function

createElement

role

Returns a description of the DOM element to be created, not a real DOM element, which contains information that tells the Vue what nodes to render on the page, including descriptions of their children. We describe such nodes as “virtual nodes”, that is, Vnodes. The tree of mapping real DOM composed of VNodes is called “virtual DOM”.

The createElement function returns a virtual node.

Parameters (3)

createElement (renderElement: String | Component, definition: Object.children: String | Array)

RenderElement: an HTML tag name, a component option object, or an async function resolve. Definition: Data object corresponding to an attribute in a template. Children: Optional VNodes, also built from 'createElement()', can also be used to generate text virtual nodes using strings. * /
Copy the code

Usage scenarios

// When can we use the render function to replace a template? // In the following scenario, the h: level template content is dynamically generated based on the level<div>// Level 1: h1, level2: h2, level3: h3<h1>{{content}}</h1>
</div>/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / if use template template generates, The page looks like this: testTemplate.vue<template>
  <h1 v-if='level === 1'>
     <slot></slot>
  </h1>
  
  <h2 v-eles-if='level === 2'>
     <slot></slot>
  </h2>
  
  <h3 v-eles-if='level === 3'>
     <slot></slot>
  </h3>.</template>// We can generate the above scenario with the following code<TestTemplate :level = '1'>How are you</TestTemplate>// But this code is very ugly when there are too many levels<slot></slot>/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / at this point we can render method to implement the above scenario<script>
  Vue.component('test-template', {
    render: function (createElement) {
      return createElement(
        'h' + this.level,   // Label name
        this.$slots.default // Array of child nodes)},props: {
      level: {
        type: Number.required: true}}})</script>The second parameter of createElement is' definition ', and the third parameter is an array of child nodes. The second parameter is ignored. Inside the createElement function, the type of the second parameter is checked. If it is an array or a non-object, the second parameter is changed to the third parameter. The second parameter is undefined. // Source location: SRC \core\vdom\create-element.jsCopy the code

Drill down into the data object (i.e., createElement the second parameter defines what)

// Official website example
{
  // Same API as' v-bind:class ',
  // Takes a string, object, or array of strings and objects
  'class': {
    foo: true.bar: false
  },
  // Same API as' v-bind:style ',
  // Accepts a string, object, or array of objects
  style: {
    color: 'red'.fontSize: '14px'
  },
  // Plain HTML attribute
  attrs: {
    id: 'foo'
  },
  / / component prop
  props: {
    myProp: 'bar'
  },
  // DOM property
  domProps: {
    innerHTML: 'baz'
  },
  // The event listener is in 'on',
  // Modifiers such as' V-on :keyup.enter 'are no longer supported.
  // keyCode needs to be checked manually in the handler.
  on: {
    click: this.clickHandler
  },
  // Only for components, for listening on native events, not for internal component use
  // 'vm.$emit' triggers the event.
  nativeOn: {
    click: this.nativeClickHandler
  },
  // Custom instruction. Note that you cannot bind 'oldValue' in 'binding'
  // Assign, because Vue has automatically synchronized for you.
  directives: [{name: 'my-custom-directive'.value: '2'.expression: '1 + 1'.arg: 'foo'.modifiers: {
        bar: true}}].// The format of the scope slot is
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props= > createElement('span', props.text)
  },
  // If the component is a child of another component, specify a name for the slot
  slot: 'name-of-slot'.// Other special top-level properties
  key: 'myKey'.ref: 'myRef'.// If you apply the same ref name to multiple elements in a render function,
  // then '$refs.myRef' becomes an array.
  refInFor: true
}
Copy the code

Render effect

The Render function is used to create a template using Javascript, focusing on the createElement parameter

How to use v-if, V-for, V-model, event, descriptor, slot, JSX, etc. in render function can be found in the official website.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — – website portal — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

update

Update (vNode); render (vNode); render (vNode); render (vNode); render (vNode);

/ / _update definition
// src\core\instance\lifecycle.js
  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
    if(! prevVnode) {// Initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)}else { 
      // Data update render
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // Omit some code
  }
Copy the code

__patch__ = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. update = vm. patch__ In web platforms it is defined in SRC \platforms\web\ Runtime \index.js

// __patch__ method definition
// src\platforms\web\runtime\index.js
Vue.prototype.__patch__ = inBrowser ? patch : noop
// An empty function is returned in a non-browser environment. The browser environment calls patch
Copy the code
// Patch method definition
// src\platforms\web\runtime\patch.js
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'

const modules = platformModules.concat(baseModules)

// Patch is the createPatchFunction that accepts an object
// Objects pass two arguments nodeOps: some dom manipulation, modules should be different platform processing (eg:weex/web)
export const patch: Function = createPatchFunction({ nodeOps, modules })
Copy the code

patch

// src\core\vdom\patch.js
export function createPatchFunction (backend) {
  let i, j
  const cbs = {}
  const { modules, nodeOps } = backend
  
  // ******** omits the auxiliary function code
  
  // Return a patch function
  // This function updates the real DOM
  /* * Params oldVnode vnode render function returns the virtual node hydrating server only removeGroup */
  return function patch (oldVnode, vnode, hydrating, removeOnly) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
      return
    }

    let isInitialPatch = false
    const insertedVnodeQueue = []

    if (isUndef(oldVnode)) { // The old node is undefined
      isInitialPatch = true
      createElm(vnode, insertedVnodeQueue) // Create it directly
    } else {
      $el is a real node, so it is true
      const isRealElement = isDef(oldVnode.nodeType) //nodeType: 1 element, 3 text, 8 comment
      // If it is not a real Node, and the old Node is the same as the old Node, the two nodes are the same by comparing the key, tag, comment Node and data information of the two nodes.
      if(! isRealElement && sameVnode(oldVnode, vnode)) {// VNode diff algorithm
        patchVnode(oldVnode, vnode, insertedVnodeQueue, null.null, removeOnly)
      } else {
        // The first rendering will include else, since vm.$el is not empty and the real node is passed in.
        if (isRealElement) {
          // Server render
          if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
            oldVnode.removeAttribute(SSR_ATTR)
            hydrating = true
          }
          if (isTrue(hydrating)) {
            if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
              invokeInsertHook(vnode, insertedVnodeQueue, true)
              return oldVnode
            } else if(process.env.NODE_ENV ! = ='production') {
              warn(
                'The client-side rendered virtual DOM tree is not matching ' +
                'server-rendered content. This is likely caused by incorrect ' +
                'HTML markup, for example nesting block-level elements inside ' +
                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                'full client-side render.')}}// either not server-rendered, or hydration failed.
          // create an empty node and replace it
          oldVnode = emptyNodeAt(oldVnode)  // Create an empty node
        }

        // replacing existing element
        const oldElm = oldVnode.elm
        const parentElm = nodeOps.parentNode(oldElm)

        // Create a node
        createElm(
          vnode,
          insertedVnodeQueue,
          // extremely rare edge case: do not insert if old element is in a
          // leaving transition. Only happens when combining transition +
          // keep-alive + HOCs. (#4590)
          oldElm._leaveCb ? null : parentElm,
          nodeOps.nextSibling(oldElm)
        )

        // Omit some code

        // Remove the old node
        if (isDef(parentElm)) { // The parentElm node was not empty the first time it was rendered, so it was removed.
          removeVnodes([oldVnode], 0.0)}else if (isDef(oldVnode.tag)) {
          invokeDestroyHook(oldVnode)
        }
      }
    }

    invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
    return vnode.elm
  }
}
Copy the code

First render breakpoint screenshot, createElm is not removeVnodes

Patch role

By observing the patch function, it can be seen that it maps to the real DOM by comparing the differences between oldVnode and VNode. The specific DIff algorithm is complicated, so I will not go into the details. Jym who is interested can learn about it.

conclusion

Learning here, combined with the previous article vUE source code to read the responsive principle, we can understand how data is to trigger the view update

  1. DefineReactive through Object. DefineProperty to make data responsive;

  2. The Dep collects dependencies in getters and dispatches updates in setters;

  3. Notify Watcher with dep.notify() and finally call vm._update(vm._render(), hydrating) to update the UI.

  4. The view triggers data updates by triggering setters of data via event methods, thus achieving bidirectional binding.