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.