Asynchronous component of vue source code analysis

Posted by MNSarahG on Tue, 14 Dec 2021 06:57:32 +0100

1, Factory function

1 when the component executes_ When the render function is converted to a virtual VNode, the createComponent() function will be executed when a component is encountered, as follows:

function createComponent (      //Line 4184 creates the component Vnode     
  Ctor,                                     //Ctor: constructor of component
  data,                                     //data: array 
  context,                                  //context:Vue instance
  children,                                 //Child: child node of the component
  tag
) {
  if (isUndef(Ctor)) {
    return
  }

  var baseCtor = context.$options._base;

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

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(("Invalid Component definition: " + (String(Ctor))), context);
    }
    return
  }

  // async component
  var asyncFactory;
  if (isUndef(Ctor.cid)) {                    //If Ctor If CID is empty, Ctor is a function, indicating that it is an asynchronous component
    asyncFactory = Ctor;                                                //Get functions for asynchronous components
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context);      //Execute the resolveAsyncComponent() function
    if (Ctor === undefined) {                                           //If Ctor is empty, calling this function returns an empty comment node
      // 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
      )
    }
  }

  /*slightly*/
  return vnode
}

For a component, such as Vue Component (component name, obj|func). The value of the component can be an object or a function. If it is an object, Vue will be executed during registration Extend() function, as follows:

if (type === 'component' && isPlainObject(definition)) {    //Line 4866 when registering a component, if the component is an object, execute Vue extend()
    definition.name = definition.name || id;
    definition = this.options._base.extend(definition);
}

To construct the basic constructor of the sub component, a cid attribute (at line 4789) will be added to the constructor, so we use cid to judge whether the component is a function.

Return to the main line, and then execute the resolveAsyncComponent() function. The factory function is related to the following:

function resolveAsyncComponent (      //Line 2283 asynchronous component factory: function of asynchronous component baseCtor: large Vue context: current Vue instance
  factory,
  baseCtor,
  context
) {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {                          //When the asynchronous component of the factory function executes here for the second time, it will return factory resolved
    return factory.resolved
  }

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

  if (isDef(factory.contexts)) {
    // already pending
    factory.contexts.push(context);
  } else {
    var contexts = factory.contexts = [context];                //Save the context as an array in contexts, that is, the current Vue instance
    var sync = true;  

    var forceRender = function () {                //Traverse all elements in contexts, and the next tick will be executed here
      for (var i = 0, l = contexts.length; i < l; i++) {      //Call the $forceUpdate() method of the element in turn, which forces rendering once
        contexts[i].$forceUpdate();
      }
    };

    var resolve = once(function (res) {                         //Define a resolve function
      // 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();
      }
    });

    var reject = once(function (reason) {                       //Define a reject function
      "development" !== 'production' && warn(
        "Failed to resolve async component: " + (String(factory)) +
        (reason ? ("\nReason: " + reason) : '')
      );
      if (isDef(factory.errorComp)) {
        factory.error = true;
        forceRender();
      }
    });

    var res = factory(resolve, reject);                       //Execute the factory() function

    if (isObject(res)) {
      /*Logic of advanced components*/
    }

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

resolveAsyncComponent will define a resolve and reject function internally, and then execute the factory() function. factory() is what we call main JS for the HelloWorld component. The require function will be executed in the function. Since require() is an asynchronous operation, resolveAsyncComponent will return undefined

Returning to resolveAsyncComponent, we give the execution of the factory() function the next breakpoint, as follows:

You can see that an undefined is returned, and finally resolveAsyncComponent() will return undefined and return to the createComponent() function. Since undefined is returned, createsyncplaceholder() will be executed to create an annotation node. After rendering, the corresponding DOM node tree is as follows:

You can see that for the factory function, the corresponding DOM node when the component is fully loaded is an annotation node

After the request() of the next tick is loaded successfully, the resolve(res) function will be executed, that is, the resolve function defined in resolveAsyncComponent(),

The resolve function will save the result to the resolved attribute of the factory function (that is, the definition of the component), and then execute the forceRender() function, that is, the code corresponding to the blue comment marked above

When re rendering is performed to resolveAsyncComponent again, the local variable factory Once resolved exists, the variable is returned directly as follows:

II. Promise loading

Promise() is relatively simple. It can be considered as a factory function to extend the knowledge of idiom sugar. It can mainly cooperate with the syntax sugar of webpack. The syntax sugar of webpack import is to return a promise object. Vue actually makes asynchronous components to cooperate with the syntax sugar of webpack to realize the trend of Promise().

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

Vue.component('HelloWorld',()=>import('./components/HelloWorld'))

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

Like the factory function, resolveAsyncComponent will be executed twice. The logic of the next tick is the same. The difference is that the logic of triggering resolve() is not correct, as follows:

function resolveAsyncComponent (          //Asynchronous component
  factory,  
  baseCtor,
  context
) {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {                              //When the first execution comes here, factory Resolved does not exist
    return factory.resolved
  }

    /*slightly*/
    var res = factory(resolve, reject);                       //Here we return an object containing then

    if (isObject(res)) {                                      
      if (typeof res.then === 'function') {                      //If res is a function, that is, when loaded in Promise()
        // () => Promise
        if (isUndef(factory.resolved)) {                            //If factory Resolved does not exist
          res.then(resolve, reject);                                  //Use the then method to specify the callback functions of resolve and reject
        }
      } else if (isDef(res.component) && typeof res.component.then === 'function') {
        /**/
      }
    }

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

In the example, the res objects returned after executing factory() are as follows:

After loading successfully, resolve will be executed. The following steps are the same as the process of factory function.  

Topics: Javascript ECMAScript Vue.js