Vue source code analysis - instance mounting process

Basic process of instance mounting

After the options are merged, Vue makes a series of function calls, including initializing the event center, defining rendering functions, and so on. In_ At the end of the init method, the object el is
Option. If the el option exists, the $mount method will be called to mount the instance.

/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
  initProxy(vm)
} else {
  vm._renderProxy = vm
}
// expose real self
vm._self = vm
// Declaration cycle initialization
initLifecycle(vm)
// Event center initialization
initEvents(vm)
// Initialize rendering
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')


// If the el option exists, the calling method is hung on the element
if (vm.$options.el) {
  vm.$mount(vm.$options.el)
}

When looking for the definition of the $mount method, we will find that this method is defined in multiple locations of the Vue source code. This is because the implementation of the $mount method is related to the platform and construction method. In this series of articles, we focus on the implementation of the $mount method in the runtime with compiler version. Let's first look at the implementation of the $mount method

// First, cache the $mount method on the Vue constructor prototype, which is a common mount method
const mount = Vue.prototype.$mount
// Redefine the $mount method for the Runtim with compiler version
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
  }

  const options = this.$options
  /**
   * If the render method is not defined, convert el or template to render method
   */
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          // Find the template element and return the template
          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) {
        // The passed in template is a dom node
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      // If the template option is not passed in, the element node corresponding to the el option is used as the template
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      // Convert template to render method
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)

      // Save the converted render method to options for subsequent use
      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')
      }
    }
  }
  // Call the mount method to mount the element
  return mount.call(this, el, hydrating)
}

In this code, the $mount method on the Vue prototype (which is a common mount method for multiple platforms and environments) is cached first, and then the method is redefined. Next, determine if there is a definition of the render method. If there is no definition of the render method, we need to get the template through the el or template option, then compile the template to get the render method, and finally call the caching method to mount the element.

Now let's look at the implementation of the cached $mount method

// platforms/web/runtime/index.js
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

It can be seen that a mountComponent method is called inside the cached $mount (the implementation of this method will be analyzed later, and the basic functions will be briefly introduced here). The core of this method is to instantiate a rendering watcher, and the updateComponent method will be called in Wathcer to render elements. The rendering Watcher will be executed in two places: one is during initialization, that is, when the instance is mounted; On the other hand, when the data in the Vue instance changes, execute again to update the page.

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode
  }
  callHook(vm, 'beforeMount')

  let updateComponent
  /* istanbul ignore if */
  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 = () => {
      // Call_ update method to convert virtual DOM into real DOM
      vm._update(vm._render(), hydrating)
    }
  }

  /**
   * Create a render watcher and execute the updateComponent callback function when the data changes
   * When the instance is first mounted, the updateComponent method will also be executed once
   */
  new Watcher(vm, updateComponent, noop, {
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        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
}

summary

Let's summarize the mounting process of Vue instances

  • Determine the mounted DOM element, which cannot be used as the root nodes such as html and body
  • Determine the rendering method. If the render method is defined in the passed in options, it can be used directly; If you define the render method for, you need to compile the template to generate the render method
  • Regardless of the source of the render method, the mountComponent will be executed at last, and a render watcher will be created at this stage
  • In the function updateComponent returned in the render watcher, there are two stages: one is to call VM_ Render method, one is to call VM_ Update method. Next, we will analyze the implementation and functions of these two methods

Keywords: Javascript Front-end Vue.js

Added by steadyguy on Wed, 02 Mar 2022 16:07:28 +0200