Vue.js source code learning – componentization (under)

The previous part analyzed componentization creation, patch and configuration merging. This part will talk about componentization life cycle, component registration and asynchronous component.

First, life cycle

Each Vue instance goes through a series of initialization procedures before being created. For example, you need to set up data listening, compile templates, mount instances to the DOM, update the DOM when data changes, and so on. There are also hook functions called lifecycle that run along the way, giving users the ability to add their own code in certain scenarios.

Here’s a look at how these lifecycle hook functions are executed from a source code perspective.

In the life cycle of function in the source are call callHook method, it is defined in SRC/core/instance/lifecycle. Js file:

export function callHook(vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}
Copy the code

$options[hook] = vm.$options[hook] = vm.$options[hook]

In the previous article, we introduced the options merge process. The life cycle functions of each stage are merged into vm.$options, which is an array. Therefore, the function of the callHook function is to call all callbacks registered by a lifecycle hook.

Now that we know how the lifecycle is executed, we’ll go into details about when each lifecycle function is called.

1.1, beforeCreate & Created

BeforeCreate and created functions are instantiated Vue stage, performed for _init method, it is defined in SRC/core/instance/init. Js file:

Vue.prototype._init = function (options? :Object) {
  // ...
  initLifecycle(vm)
  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')
  // ...
}
Copy the code

As you can see, the beforeCreate and Created hooks are called before and after initState, which initializes properties like props, data, methods, Watch, computed, and so on. Therefore, the beforeCreate hook function cannot get the values defined in props and data, nor can it call functions defined in methods.

When the two hook functions execute, the DOM is not rendered and therefore cannot be accessed.

1.2. BeforeMount & Mounted

BeforeMount hook function occurs before the DOM mount, it is the call time in mountComponent function, defined in SRC/core/instance/lifecycle. The js file:

export function mountComponent(vm: Component, el: ? Element, hydrating? : boolean) :Component {
  vm.$el = el
  // ...
  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 = () = > {
      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 && ! 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
}
Copy the code

BeforeMount hook function is executed before vm._render() is used to render VNode. Mounted hook function is executed after vm._update() is used to patch VNode to the real DOM. If vm.$vnode is null, this is not a component initialization, but an external new Vue initialization.

The mounted timing of components is analyzed below. When the VNode patch of the component is inserted into the DOM, the invokeInsertHook function is executed, and the hook function saved in insertedVnodeQueue is executed successively, which is defined in SRC /core/vdom/patch.js:

function invokeInsertHook(vnode, queue, initial) {
  // delay insert hooks for component root nodes, invoke them after the
  // element is really inserted
  if (isTrue(initial) && isDef(vnode.parent)) {
    vnode.parent.data.pendingInsert = queue
  } else {
    for (let i = 0; i < queue.length; ++i) {
      queue[i].data.hook.insert(queue[i])
    }
  }
}
Copy the code

The insert hook function is defined in SRC /core/vdom/create-component.js as componentVNodeHooks:

const componentVNodeHooks = {
  // ...
  insert(vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if(! componentInstance._isMounted) { componentInstance._isMounted =true
      callHook(componentInstance, 'mounted')}// ...
  },
  // ...
}
Copy the code

InsertedVNodeQueue (insertedVNodeQueue, insertedVNodeQueue, insertedVNodeQueue, insertedVNodeQueue, insertedVNodeQueue, insertedVNodeQueue)

1.3. BeforeUpdate & Updated

BeforeUpdate and updated hook functions should both execute when data is updated.

BeforeUpdate execution timing is in the before function of rendering Watcher, as shown in the following code:

export function mountComponent(vm: Component, el: ? Element, hydrating? : boolean) :Component {
  // ...
  new Watcher(
    vm,
    updateComponent,
    noop,
    {
      before() {
        if(vm._isMounted && ! vm._isDestroyed) { callHook(vm,'beforeUpdate')}}},true /* isRenderWatcher */
  )
  // ...
}
Copy the code

Note that the hook function will not be called until the component is mounted.

The execution of the updated timing is when flushSchedulerQueue function call, it is defined in SRC/core/observer/scheduler. The js file:

function flushSchedulerQueue() {
  // ...
  callUpdatedHooks(updatedQueue)
}

function callUpdatedHooks(queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if(vm._watcher === watcher && vm._isMounted && ! vm._isDestroyed) { callHook(vm,'updated')}}}Copy the code

The updatedQueue is an updated Watcher array. In the callUpdatedHooks function, it iterates through this array only if the watcher is vm._watcher and the component is mounted. The updated hook function is executed.

1.4, beforeDestroy & Destroyed

BeforeDestroy and destroyed a hook function execution time in component destruction stage, will eventually call $destroy methods, it is defined in SRC/core/instance/lifecycle. The js file:

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}}Copy the code

BeforeDestroy the hook function executes at the beginning of the execution of $destroy, followed by a series of destruction actions, including removing itself from parent’s $children, deleting watcher, The currently rendered VNode executes a destroyed hook function and then calls the destroyed hook function.

Destroyed hook is destroyed first and then destroyed second. Destroyed hook is destroyed in the same order as Mounted. The destroyed hook is destroyed first and then destroyed third.

Second, component registration

In vue.js, in addition to its built-in components such as keep-alive, Component, transition, transition-group, etc., custom components must be registered before they can be registered.

Vue.js provides two ways to register components, global and local. The following is the source code to analyze the two registration methods.

2.1. Global Registration

To register a global component, use Vue.component(tagName, options). Such as:

Vue.component('my-component', {
  / / options
})
Copy the code

Vue.component is defined in SRC /core/global-api/assets.js. The Vue.component function is defined in SRC /core/global-api/assets.js.

import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '.. /util/index'

export function initAssetRegisters(Vue: GlobalAPI) {
  /** * Create asset registration methods. */
  ASSET_TYPES.forEach((type) = > {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ) :Function | Object | void {
      if(! definition) {return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if(process.env.NODE_ENV ! = ='production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}
Copy the code

The ASSET_TYPES function first iterates over the ASSET_TYPES and mounts the type to Vue. ASSET_TYPE is defined in SRC /shared/constants.js:

export const ASSET_TYPES = ['component'.'directive'.'filter']
Copy the code

So in fact, Vue initializes three global functions, and if type is component and definition is an object, then this.options._base.extend, Extend converts this object to a constructor that inherits from Vue, and finally mounts it to Vue.options.components via this.options[type + ‘s’][id] = definition.

Since components are created from vue. extend, we have analyzed the following logic in the inheritance process:

Sub.options = mergeOptions(Super.options, extendOptions)
Copy the code

Options is merged into sub. options, and then the component instantiation phase executes the merge options logic. Merge Sub.options.components into VM.

The _createElement method is then executed during the vNode creation process.

export function _createElement(context: Component, tag? : string | Class<Component> |Function | Object, data? : VNodeData, children? : any, normalizationType? : number) :VNode | Array<VNode> {
  // ...
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if( process.env.NODE_ENV ! = ='production' &&
        isDef(data) &&
        isDef(data.nativeOn)
      ) {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>. `,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag),
        data,
        children,
        undefined.undefined,
        context
      )
    } else if((! data || ! data.pre) && isDef((Ctor = resolveAsset(context.$options,'components', tag)))
    ) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(tag, data, children, undefined.undefined, context)
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  // ...
}
Copy the code

IsDef (Ctor = resolveAsset(context.$options, ‘components’, tag)) In the SRC/core/utils/options. Js file:

export function resolveAsset(
  options: Object, type: string, id: string, warnMissing? : boolean) :any {
  /* istanbul ignore if */
  if (typeofid ! = ='string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if(process.env.NODE_ENV ! = ='production'&& warnMissing && ! res) { warn('Failed to resolve ' + type.slice(0, -1) + ':' + id, options)
  }
  return res
}
Copy the code

Assets = options[type] const assets = options[id] const assets = options[type] If it does not exist, change the id to a hump. If it still does not exist, change the initial letter to a capital case. If it still does not exist, an error will be reported.

Return to resolveAsset(Context.$options, ‘components’, tag). Use new Quarrels.$options. And as a parameter to the createComponent hook.

2.2. Partial registration

Vue.js also supports local registration, which can be done within a component using the Components option as follows:

import HelloWorld from './components/HelloWorld'

export default {
  components: {
    HelloWorld,
  },
}
Copy the code

There is logic to merge options in the component’s Vue instantiation phase. Merge Components into VM.Quarrels. Com.components.ponents. And as a parameter to the createComponent hook.

Local registries differ from global registries in that only components of that type can access the child components of local registries, whereas global registries are extended under vue. options, so during the creation of all components, Extension from the global Vue.options.components to the current component’s VM.code.components. ponents, which is why globally registered components can be used arbitrarily.

Asynchronous components

In practical development, in order to reduce the first screen code volume, some non-first screen components are often designed as asynchronous components and loaded on demand. Vue also supports asynchronous component capabilities, as follows:

Vue.component('async-example'.function (resolve, reject) {
  require(['./my-async-component'].function (res) {
    resolve(res)
  })
})
Copy the code

As you can see from the code, Vue no longer registers an object. Instead, it registers a factory function that takes resolve and reject arguments, possibly by dynamically requesting the JS address of an asynchronous component, and ultimately by executing the resolve method, whose arguments are our asynchronous component object.

The createComponent function does not execute vue. extend to create a component constructor because the component definition is not a normal object. The createComponent function can still be executed.

export function createComponent(
  Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag? : string) :VNode | Array<VNode> | void {
  if (isUndef(Ctor)) {
    return
  }

  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
  // ...

  // async component
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
    }
  }
  // ...
}
Copy the code

As you can see from the code, since the Ctor we passed is a function, vue.extend logic is not executed, so its CID is undefined, so it goes into asynchronous component creation logic. Ctor = resolveAsyncComponent(asyncFactory, baseCtor) It is defined in SRC/core/vdom/helpers/resolve – async – component. Js file:

export function resolveAsyncComponent(
  factory: Function,
  baseCtor: Class<Component>
) :Class<Component> | void {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {
    return factory.resolved
  }

  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }

  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
    return factory.loadingComp
  }

  if(owner && ! isDef(factory.owners)) {const owners = (factory.owners = [owner])
    let sync = true
    let timerLoading = null
    let timerTimeout = null; (owner: any).$on('hook:destroyed'.() = > remove(owners, owner))

    const forceRender = (renderCompleted: boolean) = > {
      for (let i = 0, l = owners.length; i < l; i++) { ; (owners[i]: any).$forceUpdate() }if (renderCompleted) {
        owners.length = 0
        if(timerLoading ! = =null) {
          clearTimeout(timerLoading)
          timerLoading = null
        }
        if(timerTimeout ! = =null) {
          clearTimeout(timerTimeout)
          timerTimeout = null}}}const resolve = once((res: Object | Class<Component>) = > {
      // cache resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if(! sync) { forceRender(true)}else {
        owners.length = 0}})const reject = once((reason) = >{ process.env.NODE_ENV ! = ='production' &&
        warn(
          `Failed to resolve async component: The ${String(factory)}` +
            (reason ? `\nReason: ${reason}` : ' '))if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender(true)}})const res = factory(resolve, reject)

    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
        res.component.then(resolve, reject)

        if (isDef(res.error)) {
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }

        if (isDef(res.loading)) {
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            factory.loading = true
          } else {
            timerLoading = setTimeout(() = > {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)}}if (isDef(res.timeout)) {
          timerTimeout = setTimeout(() = > {
            timerTimeout = null
            if(isUndef(factory.resolved)) { reject( process.env.NODE_ENV ! = ='production'
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }

    sync = false
    // return in case resolved synchronously
    return factory.loading ? factory.loadingComp : factory.resolved
  }
}
Copy the code

This function is a bit more complicated because it actually handles three different ways of creating asynchronous components. In addition to the previous factory function, two more are supported. One supports the way that Promises create components, as follows:

Vue.component('async-webpack-example'.() = > import('./my-async-component'))
Copy the code

The other is the advanced asynchronous component, as follows:

const AsyncComp = () = > ({
  component: import('./MyComp.vue'),
  loading: LoadingComp,
  error: ErrorComp,
  delay: 200.timeout: 3000,
})
Vue.component('async-example', AsyncComp)
Copy the code

ResolveAsyncComponent logic: resolveAsyncComponent logic

3.1 ordinary function asynchronous components

In the case of ordinary functions, the previous if judgments can be ignored; they are for use by higher-level components. Then we go to the actual loading logic and define the forceRender, resolve, and reject functions, which are wrapped in once and defined in SRC /shared/util.js:

export function once(fn: Function) :Function {
  let called = false
  return function () {
    if(! called) { called =true
      fn.apply(this.arguments)}}}Copy the code

The logic of the once function, which passes in a function and returns a new one, uses a closure and a flag bit to ensure that the wrapped function is executed only once, ensuring that the resolve and reject functions are executed only once.

We then execute the const res = factory(resolve, reject) logic, which executes the component’s factory function, passing resolve and reject as arguments, Resolve (res) ¶ Factory.resolved = ensureCtor(res, baseCtor) :

function ensureCtor(comp: any, base) {
  if (comp.__esModule || (hasSymbol && comp[Symbol.toStringTag] === 'Module')) {
    comp = comp.default
  }
  return isObject(comp) ? base.extend(comp) : comp
}
Copy the code

The purpose of the Vue. Extend () function is to ensure that the component object defined by the asynchronous component JS can be found and, if it is a normal object, converted to a component constructor by calling vue.extend.

The resovle logic finally determines sync, which is clearly false in this scenario, so the forceRender function is executed, which takes each instance vm that calls the asynchronous component, and then executes the vm.$forceUpdate() method. Its definition in the SRC/core/instance/lifecycle. The js file:

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

The logic of $forceUpdate is to call the render Watcher update method, which causes the render Watcher callback to execute, triggering a re-rendering of the component. Because Vue is data-driven view re-rendering, but no data changes during the entire asynchronous component load, you can force the component to re-render by executing $forceUpdate.

3.2. Promise asynchronous components

Vue.component(
  'async-webpack-example'.// The 'import' function returns a 'Promise' object.
  () = > import('./my-async-component'))Copy the code

Webpack 2+ supports syntactic sugar for asynchronous loading: () => import(‘./my-async-component’), res = factory(resolve, reject), import(‘./my-async-component’) It’s a Promise object. Typeof res.then === ‘function’ typeof res.then === ‘function’ typeof res.then === ‘function’

if (isUndef(factory.resolved)) {
  res.then(resolve, reject)
}
Copy the code

If the load succeeds, run resolve. If the load fails, run Reject.

3.3. Advanced Asynchronous Components

In the development process, asynchronous loading components need to load JS dynamically, which leads to certain network delay and loading failure. Therefore, loading and error components need to be designed and rendered at an appropriate time. In this case, Vue provides an advanced asynchronous component approach that allows loading and error components to be rendered with a simple object configuration. Let’s look at how advanced asynchronous components are implemented.

const AsyncComp = () = > ({
  component: import('./MyComp.vue'), // Load the component that should be rendered
  loading: LoadingComp, // Render component when error occurs
  error: ErrorComp, // Render the wait time before loading the component. Default value: 200ms.
  delay: 200.// Maximum waiting time. Beyond this time the error component is rendered. Default: Infinity
  timeout: 3000,
})
Vue.component('async-example', AsyncComp)
Copy the code

The initialization logic of advanced asynchronous components is the same as that of ordinary asynchronous components. When res = factory(resolve, reject) is executed, the return value is the defined component object. Else if(isPromise(res.component.reject)), then res.component.then(resolve, reject), resolve when the asynchronous component loads successfully, reject when it fails.

Since asynchronous component loading is an asynchronous process, it then synchronously executes the following logic:

if (isDef(res.error)) {
  factory.errorComp = ensureCtor(res.error, baseCtor)
}

if (isDef(res.loading)) {
  factory.loadingComp = ensureCtor(res.loading, baseCtor)
  if (res.delay === 0) {
    factory.loading = true
  } else {
    timerLoading = setTimeout(() = > {
      timerLoading = null
      if (isUndef(factory.resolved) && isUndef(factory.error)) {
        factory.loading = true
        forceRender(false)
      }
    }, res.delay || 200)}}if (isDef(res.timeout)) {
  timerTimeout = setTimeout(() = > {
    timerTimeout = null
    if(isUndef(factory.resolved)) { reject( process.env.NODE_ENV ! = ='production'
          ? `timeout (${res.timeout}ms)`
          : null
      )
    }
  }, res.timeout)
}
Copy the code

Determine if res.error defines an error component, and if so, assign it to factory.errorcomp. Res. loading = true; res.delay = 0; loadingComp = true; Otherwise delay the execution of the delay time.

Finally, judge res.timeout. If this parameter is configured, reject if the component is not successfully loaded after res.timeout.

At the end of the resolveAsyncComponent there is logic:

sync = false

return factory.loading ? factory.loadingComp : factory.resolved
Copy the code

If delay is set to 0, the loading component will be rendered this time, otherwise the forceRender will be delayed, and resolveAsyncComponent will be executed again.

In this logical order of execution, there are several different situations.

3.3.1 Asynchronous component loading failed

When the asynchronous component fails to load, the reject function is executed:

const reject = once((reason) = >{ process.env.NODE_ENV ! = ='production' &&
    warn(
      `Failed to resolve async component: The ${String(factory)}` +
        (reason ? `\nReason: ${reason}` : ' '))if (isDef(factory.errorComp)) {
    factory.error = true
    forceRender(true)}})Copy the code

Error is set to true and forceRender() is executed again to resolveAsyncComponent:

if (isTrue(factory.error) && isDef(factory.errorComp)) {
  return factory.errorComp
}
Copy the code

ErrorComp is returned, rendering the error component directly.

3.3.2 Asynchronous components are loaded successfully

When the asynchronous component loads successfully, the resolve function is executed:

const resolve = once((res: Object | Class<Component>) = > {
  factory.resolved = ensureCtor(res, baseCtor)
  if(! sync) { forceRender(true)}else {
    owners.length = 0}})Copy the code

As sync is false, execute forceRender() to resolveAsyncComponent again:

if (isDef(factory.resolved)) {
  return factory.resolved
}
Copy the code

Return to factory.Resolved and render the successfully loaded component.

3.3.3 Asynchronous component loading

If the asynchronous component does not return from the load, this logic will be used:

if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
  return factory.loadingComp
}
Copy the code

LoadingComp is returned, and the loading component is rendered.

3.3.4 Loading timeout of asynchronous components

If it times out, it goes to the Reject logic, which renders the error component as if it had failed to load.

3.4 Asynchronous component patch

Back to createComponent’s logic:

Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
  return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
}
Copy the code

The first time resolveAsyncComponent is executed, undefined is returned unless a loading component is created using the advanced async component 0 delay. Then create a comment node with createAsyncPlaceholder as a placeholder. It is defined in SRC/core/vdom/helpers/resolve – async – component. Js file:

export function createAsyncPlaceholder(
  factory: Function, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag: ? string) :VNode {
  const node = createEmptyVNode()
  node.asyncFactory = factory
  node.asyncMeta = { data, context, children, tag }
  return node
}
Copy the code

This essentially creates a placeholder annotation VNode and assigns asyncFactory and asyncMeta to the current VNode.

When forceRender is executed, the component will be rerendered, and resolveAsyncComponent will be executed again. Loading, error, or successfully loaded asynchronous components will be returned, depending on the case. Therefore, the normal component render, patch process.

Four,

The essence of asynchronous component implementation is two-time rendering. Except for the advanced asynchronous component with 0 delay, which is directly rendered into loading component for the first time, all other components are rendered to generate a annotation node for the first time. When the component is successfully obtained asynchronously, it is forced to re-render through forceRender. This will render the components we load asynchronously correctly.

At this point, the componentized lifecycle, component registration, and asynchronous components have been analyzed, and the entire componentized part has been analyzed.