Detailed explanation of Vue source code: preparations before generating 'Vue' instances

Detailed explanation of Vue source code (I): preparations before generating Vue instances

1. Start with new Vue()

vue/src/core/index.js :

import Vue from './instance/index' // 1. Introduce Vue constructor
import { initGlobalAPI } from './global-api/index' // 2. Introduce the dependency to initialize the global API
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

initGlobalAPI(Vue) // 3. Initialize global API

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})

Vue.version = '__VERSION__'

export default Vue

Since you start with new Vue(), you must first understand the constructor of Vue. As can be seen from the above code Note 1, Vue's constructor is in Vue / SRC / core / instance / index JS, the source code is as follows:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options) // 1. Vue instance initialization
}

initMixin(Vue) // 2
stateMixin(Vue) // 3
eventsMixin(Vue) // 4
lifecycleMixin(Vue) // 5
renderMixin(Vue) // 6

export default Vue

Note 1: when new Vue(), only one initialization work is performed this_ init(options) ; It is worth noting that after the constructor is defined, there is no call to new Vue at this time, that is, before the instance is created, the initialization at note 2 3 4 5 6 will be executed to initialize the global API. So far, the preparations are ready. When the Vue instance is generated by calling new Vue, this will be called_ init(options) . Next, explore what Vue did in turn before generating an instance.

1.1 initMixin (vue\src\core\instance\init.js)

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this // 1. vm is this, the instance object of Vue
    // a uid
    vm._uid = uid++ // Each Vue instance object can be regarded as a component, and each component has one_ uid attribute to mark uniqueness

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    vm._isVue = true
    // merge options 
    // Merge parameters. options are the parameters passed in when we call 'new Vue ({El:' app 'chuand,... Args})'
    // After the merge is completed, mount the merge results to the current 'Vue' instance
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions( // After the merge is completed, mount the merge results to the current 'Vue' instance
        // This function will check whether the current Vue instance has an earlier function and the options options on its parent class and ancestor class, and can listen for changes and combine the options of the ancestor class, parent class and the current Vue instance
        resolveConstructorOptions(vm.constructor), 
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm) // 1. Initialize the declaration cycle
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

1.1.1 initLifecycle

The above code marks each instance with a unique_ uid attribute, and then mark whether it is a Vue instance. After merging the parameters passed in by the user and Vue's own parameters, mount it to the $options attribute of Vue.

export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  // This annotation is very clear. It is to find the first non Abstract parent component of the current vue instance
  // When found, the current component will be merged into the ` $children 'array of the parent component
  // Thus, the parent-child relationship of components is established
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null // These two ignore first. I don't know why
  vm._directInactive = false // These two ignore first. I don't know why
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

As mentioned above, when initializing the declaration cycle, the parent-child relationship between the current component and other components will be described. If the parent component is found, the $root pointer will point to the parent component. If it is not found, it will point to the current Vue instance. Next, VM$ Children = [] initialize sub component list, VM$ Refs = {} initialize reference list, VM_ Watcher = null initializes the observer list. At this time, there is no observer, and data changes cannot be detected. VM_ Ismounted = false indicates that the current component has not been mounted to DOM, VM_ Isdestroyed = false indicates that the current component is not a destroyed instance, which is related to garbage collection, VM_ Isbeingdestroyed = false indicates whether the current component is destroying work.

At this point, the initialization of the declaration cycle has been completed.

1.1.2 initEvents

vue/src/core/instance/events.js :

1.2 stateMixin: status initialization

vue/src/core/instance/state.js :

export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.',
        this
      )
    }
    propsDef.set = function () {
      warn(`$props is readonly.`, this)
    }
  }
  Object.defineProperty(Vue.prototype, '$data', dataDef) // 1
  Object.defineProperty(Vue.prototype, '$props', propsDef) // 2

  Vue.prototype.$set = set // 3
  Vue.prototype.$delete = del // 4

  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}

The above code initializes the state of the instance. In notes 1 and 2, two attributes of $data $props are added to the Vue prototype object respectively, and the values of these two attributes are the values of the current vm_ data _props property, and setting these two properties cannot be modified.

In note 3 and 4, set and delete methods are added to vm. There is no need to introduce what set and delete are. Vue objects also have Vue Set and Vue Both delete methods are derived from the function set below. Their functions are reflected in 1 and 2 of the code comments below:

The parameter target is an object or array, and the target has a__ ob__ Attribute. The source of this attribute is the constructor in the Observer class. One sentence is def (value, '_ob_', this). Value is the object to be observed, that is, the attribute in the data passed in when we write code, and then the data we pass in is actually managed__ ob__ This attribute is used. In the future, when we operate on the data in data or access the data in data, it will be proxied__ ob__ This property.

Then the $watcher method is mounted on the prototype object, and the return value of this method is a method to destroy the watcher. As for what the watcher is and its role, we'll talk about it later.

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val) // 1
  ob.dep.notify() // 2
  return val
}

vue\src\core\util\lang.js :

/**
 * Define a property.
 */
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}

1.2.3 watcher

vue/src/core/observer/watcher.js: it can be said to be one of the quintessence of Vue. It is based on observer mode and publish subscribe mode, and uses object. In ES5 specification The defineproperty interface realizes the observation of data and the automatic update of view. Let's analyze it bit by bit.

/* @flow */

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  noop
} from '../util/index'

import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'

import type { SimpleSet } from '../util/index'

let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep
      this.user = !!options.user
      this.lazy = !!options.lazy
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    this.value = this.lazy
      ? undefined
      : this.get()
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      if (this.deep) {
        traverse(value)
      }
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface.
   * Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value
        this.value = value
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate () {
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   */
  depend () {
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend()
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

1.3 event initialization

In fact, some methods ($on $once $off $emit) are mounted on the Vue prototype object, and an event response system is implemented based on the publish subscribe mode, which is very similar to the eventEmitter in nodejs. This is the source of our common event bus mechanism.

Simply parse the following code:

$on is an event subscription. From its parameters (event: string | array < string >, FN: function), you can subscribe to multiple events at one time. They share a processing function, and then store all processing functions in VM in the form of key value pairs ({eventName: handler []})_ In the events object, wait for the event to be published. Once the event is published, it will go to the event handler list (handler []) according to the event type (eventName), read the handler and execute it.

$emit is the release of events. In the production environment, the case of event names (i.e. types) is converted. There is no need to distinguish the case of event names. Of course, we can't write them in such a rough way. Then cbs is the list of processing functions read according to the event name. const args = toArray(arguments, 1) is the parameter for processing the event. The function toArray removes the first parameter of the $emit function and finally passes it into our subscription function. Namely
vm.$ The final call result of emit ('render ',' a ', 124) code is VM_ All functions in the events ['render '] list run once with ('a', 123) as parameters.

$off is to delete the subscription function of the event from the subscription list. It provides two parameters (event?: string | array < string >, fn?: function). Both parameters are optional and cannot only wear the second parameter. If the argument list is empty, all subscribed events and event handling functions on the current vm will be deleted; If the second parameter is empty, the vm of the current vm_ All processing functions in events [event] will be cleared; If the second parameter fn is not empty, only vm_ Events [event] the fn function in the event handling list is deleted.

$once means that event processing is executed only once. If events are published multiple times, the processing function will be executed only once. This function is a little tricky. First create an on function, and then mount the event handling function fn to the function object. The function is also an object and can have its own properties. There is no doubt about this. There are only two sentences of code vm in the on function$ Off (event, on), let vm unsubscribe from the on function, which can ensure that the on function will not be executed in the future; Next sentence FN Apply (vm, arguments) calls FN, which ensures that FN is executed once. Hahaha, 666

This completes the initialization of the event.

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (!fn) {
      vm._events[event] = null
      return vm
    }
    // specific handler
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break
      }
    }
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      const info = `event handler for "${event}"`
      for (let i = 0, l = cbs.length; i < l; i++) {
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}

1.4 lifecycle mixin lifecycle initialization

The code is as follows. Three methods are added to the prototype object of Vue_ update $forceUpdate $destroy, take a look at what has been done in turn.

vm._update passed__ patch__ The function compiles the virtual node vnode into a real DOM In addition, the component update is also here to complete the transformation from the virtual node to the real DOM. After the parent component is updated, the child component will also be updated.

vm.$forceUpdate if there are observers on the current component, the component will be refined directly.

vm.$destroy destroys components. If the current component is going through the destruction process, it will return directly and wait for continued destruction. Otherwise, the beforeDestroy declaration cycle will be triggered and the current component will be marked as being destroyed. Then delete the current component from the parent component, destroy all watcher s, and destroy VM_ data__ ob__ , Mark the component status as destroyed, regenerate the real DOM, trigger the destroyed life cycle method, remove the events subscribed by the current component and the event handler, and clear the reference of the current component to the parent component.

vue/src/core/instance/lifecycle.js

export function lifecycleMixin (Vue: Class<Component>) {
  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
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    restoreActiveInstance()
    // update __vue__ reference
    if (prevEl) {
      prevEl.__vue__ = null
    }
    if (vm.$el) {
      vm.$el.__vue__ = vm
    }
    // if parent is an HOC, update its $el as well
    if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
      vm.$parent.$el = vm.$el
    }
    // updated hook is called by the scheduler to ensure that children are
    // updated in a parent's updated hook.
  }

  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy')
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    // teardown watchers
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    // remove reference from data ob
    // frozen object may not have observer.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)
    // fire destroyed hook
    callHook(vm, 'destroyed')
    // turn off all instance listeners.
    vm.$off()
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }
}

1.5 renderMixin

It also mounts some methods to Vue's prototype object.

Installrender helpers (Vue. Prototype) adds some methods required for template parsing and compilation to vm;

$nextTick is this we often use when writing code$ nextTick(), which returns a Promise instance p. we can access the data updated to the DOM element in the then function of p, or send it to this nextTick passes a callback function f, which can also access the data updated to the DOM element.

_ The render method generates a virtual node. See code below for details.

vue/src/core/instance/render.js

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype)

  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }

  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options

    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // if the returned array contains only a single node, allow it
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}

Keywords: Javascript Front-end Vue.js Design Pattern

Added by delassus on Wed, 05 Jan 2022 05:52:44 +0200