Vue source notes - data driven - Mount Vue instance

Vue version: 2.5.17-beta.0

Before analyzing the mounting method vm.$mount() of Vue instance, we have to mention two versions of Vue:

  1. Runtime + compiler
  2. Include only runtime

The official Vue documentation also describes the differences between the two versions( Runtime + compiler vs. contains only runtime ), vm.$mount() is implemented in different ways according to different versions. This paper analyzes it according to the version of runtime + compiler.

As mentioned in the previous article( Vue source notes - data driven - what happened to new Vue )The last step is to call vm.$mount(vm.$options.el) to mount and render the real dom.
In the version of runtime + compiler, $mount prototype method is defined in src/platforms/web/entry-runtime-with-compiler.js file. The source code is as follows:

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  // options.render is available for vue components because vue loader will compile and generate a render
  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    // template ends with dom string
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      // Compile related content
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}

As can be seen from the above source code, first, a copy of Vue.prototype.$mount is cached using the mount variable, and the cached $mount is the mount method in the runtime version, which will be analyzed later. Then rewrite the $mount prototype method. As you can see in the method, the query(el) method will be called by passing in the el parameter to return the real dom node, and judge whether the node is body or html. If so, a warning will be given to return this. Why not mount it to body or html? Because the concept of Virtual DOM is introduced in Vue 2.x, after the first rendering, the Delete the incoming dom node, which is why it is not allowed. Then we will look for the options.render option (I have to mention that the Vue option can pass in the render or template option. When only the template option is passed in, Vue will get its dom string and compile it to generate the render function and then mount it. This is the difference between the runtime + compiler and the runtime version only). If there is no options.render option, you will try to find options.template and get the dom string. If there is no options.template, you will get the dom string through the el option. At this time, if the template variable has a value, you will call the compileToFunctions method to try to generate the render function And assign it to this.$options. Finally, the $mount method of the previous cache runtime is called.
Through the analysis, it can be concluded that the main task of $mount at compile time is to find out whether there is a render function. If there is no, the render function will be generated by compiling the compileToFunctions function.

The following analyzes the $mount prototype method at runtime. The $mount prototype method at runtime is defined in the src/platforms/web/runtime/index.js file. The source code is as follows:

// The runtime only version is implemented here directly
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

The run-time $mount is relatively simple, but why to find the el again is because when using the run-time version only, the mount prototype method is called directly, so you need to call the query(el) method again to find the real dom node, and then the mountcomponent (this, el, drafting) will be called.
The mountComponent method is defined in the src/core/instance/lifecycle.js file. The source code is as follows:

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  // dom object if el is not undefined
  // Cache el to vm.$el
  vm.$el = el
  // Vue finally recognizes only the render function
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      // Write template but use runtime only version to warn
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        // No template, no render warning
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  // Performance buried point correlation can be ignored
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`vue ${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`vue ${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      // Rendering: server rendering related
      // Mount to final dom
      vm._update(vm._render(), hydrating)
    }
  }

  // we set this to vm._watcher inside the watcher's constructor
  // since the watcher's initial patch may call $forceUpdate (e.g. inside child
  // component's mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

The general logic of the above source code is to cache el to vm.$el, judge the render function once, and finally create the updateComponent function, and establish the render Watcher to pass updateComponent as a callback function.
Among them, rendering Watcher is an important core content responsive of Vue, which will be recorded later. It will eventually call the incoming callback function, that is, the current updateComponent function.
The updateComponent function mainly does two things. The first is to generate vnode objects through VM. ﹣ render(), and the second is to mount them to the final dom through VM. ﹣ update (VM. ﹣ render(), drafting). In the future, the implementation of VM. ﹣ render and VM. ﹣ update will be recorded in detail.

At this point, the Vue instance mount has been analyzed.

Keywords: Javascript Vue

Added by pr0x on Wed, 27 Nov 2019 12:20:41 +0200