preamble
In the previous postGive me 5 minutes and I promise to teach you to dynamically load remote components in vue3!In the article, we went through thedefineAsyncComponent
Implemented dynamic loading of remote components. In this article we will take you through the debug source code to figure out thedefineAsyncComponent
is how to implement asynchronous components. Note: The version of vue used in this article is3.4.19
Ouyang wrote an open source ebookvue3 Compilation Principles Revealed, this book is accessible to middle school level front end. Totally free, just asking for a STAR.
Look at the demo.
It's the same routine. Let's take a look at adefineAsyncComponent
Demos for asynchronous components.
local subassemblyThe code is as follows:
<template>
<p> I am a local component</p>
</template>
asynchronous subassemblyThe code is as follows:
<template>
<p> I'm an asynchronous component </p>
</template>
parent componentThe code is as follows:
<template>
<LocalChild />
<button @click="showAsyncChild = true">load async child</button>
<AsyncChild v-if="showAsyncChild" />
</template>
<script setup lang="ts">
import { defineAsyncComponent, ref } from "vue";
import LocalChild from "./";
const AsyncChild = defineAsyncComponent(() => import("./"));
const showAsyncChild = ref(false);
</script>
We have two subcomponents here, the first, he's the same as the components we normally use, nothing more to say.
The second subcomponent is theIn the parent component, we don't have the same
Instead of importing at the top like that, you can import in the
defineAsyncComponent
Dynamically imported in the received callback function.file, defined in this way
AsyncChild
A component is an asynchronous component.
As you can see in the template, only when clicking on theload async child
The asynchronous component is loaded only after the buttonAsyncChild
。
Let's take a look at the execution as shown in the following gif:
As you can see from the gif above, when we click on theload async child
button in the network panel to load the asynchronous component.。
defineAsyncComponent
In addition to receiving a callback function that returns a Promise as above, you can also receive an object as a parameter. demo code is as follows:
const AsyncComp = defineAsyncComponent({
// loader function
loader: () => import('. /'),
// Component to use when loading asynchronous components
loadingComponent: LoadingComponent, // the component to use when loading asynchronous components.
// Show the delay before loading the component, defaults to 200ms.
delay: 200, // The delay before displaying the loaded component, defaults to 200ms.
// The component to display when loading fails
errorComponent: ErrorComponent, // the component to display if loading fails.
// If a timeout is provided and is exceeded, the error component configured here // will also be displayed.
// The error component configured here will also be displayed, default: Infinity
timeout: 3000
})
where the object parameter has several fields:
-
loader
field actually corresponds to the callback function in the previous kind of writeup. -
loadingComponent
for the loading component to be displayed during the loading of the asynchronous component. -
delay
The delay time for displaying the loading component, default is 200ms, this is because when the network condition is good, the loading is completed very fast, the replacement between the loading component and the final component is too fast may produce flickering, instead of affecting the user's feeling. -
errorComponent
is the component that is displayed after a load failure. -
timeout
is the timeout period.
In the next source code analysis, we still use the previous example of receiving a callback function that returns a Promise to debug the source code.
point of interruption (math.)
Let's move on to the parent component in the browserAfter compiling the code, it is very simple, in the browser you can use command (control in windows) + p like vscode to evoke an input box, and then in the input box, enter
Clicking enter opens the source panel with the compiled
file now. The figure below:
We see that the compiledThe file code is as follows:
import { defineComponent as _defineComponent } from "/node_modules/.vite/deps/?v=868545d8";
import {
defineAsyncComponent,
ref,
} from "/node_modules/.vite/deps/?v=868545d8";
import LocalChild from "/src/components/defineAsyncComponentDemo/?t=1723193310324";
const _sfc_main = _defineComponent({
__name: "index",
setup(__props, { expose: __expose }) {
__expose();
const showAsyncChild = ref(false);
const AsyncChild = defineAsyncComponent(() =>
import("/src/components/defineAsyncComponentDemo/")
);
const __returned__ = { showAsyncChild, AsyncChild, LocalChild };
return __returned__;
},
});
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
// ...an omission
}
export default _export_sfc(_sfc_main, [["render", _sfc_render]]);
From the code above you can see that the compiledIt is divided into two main sections, the first of which is
_sfc_main
in an objectsetup
method, which corresponds to ourscript
Module. The second block is the_sfc_render
, which is often referred to as the render function, corresponds to the contents of the template.
We're trying to figure it out.defineAsyncComponent
method, then of course it is a good idea to give the setup method thedefineAsyncComponent
method breaks the point. Refresh the page, at which point the code will stay at the breakpointdefineAsyncComponent
Methodology Division.
defineAsyncComponent
methodologies
Then walk the breakpoint into thedefineAsyncComponent
function internally, in our scenario the simplifieddefineAsyncComponent
The function code is as follows:
function defineAsyncComponent(source) {
if (isFunction(source)) {
source = { loader: source };
}
const { loader, loadingComponent, errorComponent, delay = 200 } = source;
let resolvedComp;
const load = () => {
return loader()
.catch(() => {
// ...an omission
})
.then((comp) => {
if (
comp &&
(comp.__esModule || comp[] === "Module")
) {
comp = ;
}
resolvedComp = comp;
return comp;
});
};
return defineComponent({
name: "AsyncComponentWrapper",
setup() {
const instance = currentInstance;
const loaded = ref(false);
const error = ref();
const delayed = ref(!!delay);
if (delay) {
setTimeout(() => {
= false;
}, delay);
}
load()
.then(() => {
= true;
})
.catch((err) => {
onError(err);
= err;
});
return () => {
if ( && resolvedComp) {
return createInnerComp(resolvedComp, instance);
} else if ( && errorComponent) {
return createVNode(errorComponent, {
error: ,
});
} else if (loadingComponent && !) {
return createVNode(loadingComponent);
}
};
},
});
}
As you can see from the code abovedefineAsyncComponent
It is divided into three parts.
-
Part 1: Processing incoming parameters.
-
The second part is:
load
function is used to load asynchronous components. -
Part III reads: Return
defineComponent
Defined components.
Part I: Handling incoming parameters
Let's look at the first part: handling incoming parameters. The code is as follows:
function defineAsyncComponent(source) {
if (isFunction(source)) {
source = { loader: source };
}
const { loader, loadingComponent, errorComponent, delay = 200 } = source;
let resolvedComp;
// ...an omission
}
first usingisFunction(source)
Determine the incomingsource
is not a function, and if it is, then thesource
Rewrite to includeloader
The object of the field:source = { loader: source }
. Then use theconst { loader, loadingComponent, errorComponent, delay = 200 } = source
Deconstruct the corresponding loading component, loading failure component, and delay time.
I think you can see why.defineAsyncComponent
The parameters received by the function can be either a callback function or a callback function containing theloader
、loadingComponent
、errorComponent
objects with fields like. This is because if we pass in a callback function, internally we will assign the incoming callback function to theloader
fields. However, parameters such as the loading component, load failure component, etc. will not have values, only thedelay
The delay time is given 200 by default.
Then comes the definition ofload
function is used to load asynchronous components, this function is in the third part of thedefineComponent
so let's start with thedefineComponent
Function section.
Part 3: Returns the component defined by defineComponent
Let's see.defineAsyncComponent
The return value of thedefineComponent
defined component with the following code:
function defineAsyncComponent(source) {
// ...an omission
return defineComponent({
name: "AsyncComponentWrapper",
setup() {
const instance = currentInstance;
const loaded = ref(false);
const error = ref();
const delayed = ref(!!delay);
if (delay) {
setTimeout(() => {
= false;
}, delay);
}
load()
.then(() => {
= true;
})
.catch((err) => {
onError(err);
= err;
});
return () => {
if ( && resolvedComp) {
return createInnerComp(resolvedComp, instance);
} else if ( && errorComponent) {
return createVNode(errorComponent, {
error: ,
});
} else if (loadingComponent && !) {
return createVNode(loadingComponent);
}
};
},
});
}
defineComponent
The function's received argument is a vue component object and the return value is also a vue component object. He doesn't really do much, simply just providing type derivation for ts.
Let's move on to the vue component object, which has only two fields in it:name
attributes andsetup
function.
name
attribute is familiar and indicates the name of the current vue component.
people usually<script setup>
Syntactic sugar is used more often, this syntactic sugar is compiled to be thesetup
function, and of course vue supports letting us write our own handwrittensetup
function.
Ask a question:setup
function corresponds to the<script setup>
We usually write code have template module corresponds to the view part, that is, familiar with the render function. Why is there no render function here?
do sth (for sb)setup
function to make a breakpoint so that when the asynchronous component is rendered, it will execute thissetup
function. The code will stop at thesetup
function at the breakpoint.
existsetup
function that first uses theref
Three responsive variables are defined:loaded
、error
、delayed
。
-
loaded
is a boolean value that serves to record whether the asynchronous component has finished loading. -
error
records the error message logged when the load fails, if it is also passed in theerrorComponent
component, which is displayed when loading an asynchronous component failserrorComponent
Component. -
delayed
is also a boolean value, since the loading component is not displayed immediately, but after a delay. Thisdelayed
The boolean value records whether or not it is still in the delay phase, if it is then the loading component will not be shown.
Next, determine which of the incoming parameters sets the setting of thedelay
delay, if so use thesetTimeout
latencydelay
milliseconds before thedelayed
is set to false when the value ofdelayed
value is false, the loading component will only be displayed during the loading phase. The code is as follows:
if (delay) {
setTimeout(() => {
= false;
}, delay);
}
The next step is to executeload
function, thisload
function is what we talked about earlierdefineAsyncComponent
The second part of the code in the function. The code is as follows:
load()
.then(() => {
= true;
})
.catch((err) => {
onError(err);
= err;
});
As you can see from the code aboveload
function obviously returns a Promise, which is why it can be used later with the.then()
cap (a poem).catch()
. And here in the.then()
air marshalloaded
value is set to true, the breakpoint walks into theload
function with the following code:
const load = () => {
return loader()
.catch(() => {
// ...an omission
})
.then((comp) => {
if (
comp &&
(comp.__esModule || comp[] === "Module")
) {
comp = ;
}
resolvedComp = comp;
return comp;
});
};
Here.load
The function code is also very simple to execute directly inside theloader
Functions. Remember this.loader
Is the function what it is?
defineAsyncComponent
function can receive an asynchronous load function that can go to import to import the component at runtime. This asynchronous load function is theloader
function that executes theloader
function then goes and loads the asynchronous component. In our case it's asynchronous loadingcomponent with the following code:
const AsyncChild = defineAsyncComponent(() => import("./"));
So here's how to executeloader
function is the function that executes the() => import("./")
The execution of theimport()
After that it can be used in thenetwork
The panel sees the loadingWeb requests for documents.
import()
The return is a Promise, which will be triggered when the imported file is loaded.then()
So here'sthen()
will not be triggered at this point.
Then take the breakpoint out of theload
The function returns to thesetup
The last return part of the function has the following code:
setup() {
// ...an omission
return () => {
if ( && resolvedComp) {
return createInnerComp(resolvedComp, instance);
} else if ( && errorComponent) {
return createVNode(errorComponent, {
error: ,
});
} else if (loadingComponent && !) {
return createVNode(loadingComponent);
}
};
},
Notice how thesetup
The return value is a function, not an object as we often see. Since the return here is a function, the code will not go to the returned function to go inside the function to return to the function to make a breakpoint. We do not look at the function for the time being, so that the breakpoint out of thesetup
function. Discoversetup
function is created from the vuesetupStatefulComponent
function call, in our scenario the simplifiedsetupStatefulComponent
The function code is as follows:
function setupStatefulComponent(instance) {
const Component = ;
const { setup } = Component;
const setupResult = callWithErrorHandling(setup, instance, 0, [
,
setupContext,
]);
handleSetupResult(instance, setupResult);
}
uppercallWithErrorHandling
A function, as you should be able to tell from the name, calls a function and does error handling. In this case it's calling thesetup
function, which will then call thesetup
The return value of the function is thrown to thehandleSetupResult
function processing.
Walk the breakpoint into thehandleSetupResult
function, in our scenariohandleSetupResult
The simplified code for the function is as follows:
function handleSetupResult(instance, setupResult) {
if (isFunction(setupResult)) {
= setupResult;
}
}
Earlier we talked about our scenariosetup
The return value of a function is a function, soisFunction(setupResult)
The code will go to the = setupResult
Here.instance
is the current instance of the vue component, and executing this will set thesetupResult
assign a value torender
function.
We know that the render function is generally compiled from the template module, the implementation of the render function will generate the virtual DOM, and finally generated by the virtual DOM corresponding to the real DOM.
(coll.) fail (a student)setup
is a function, that function acts as the component's render function. This is why the previousdefineComponent
Onlyname
familiarity with andsetup
function without therender
function.
When the render function is executed to generate the virtual DOM it goes to thesetup
The code will stop at the function returned by thesetup
returned in the function. Recall that thesetup
The returned function code is as follows:
setup() {
// ...an omission
return () => {
if ( && resolvedComp) {
return createInnerComp(resolvedComp, instance);
} else if ( && errorComponent) {
return createVNode(errorComponent, {
error: ,
});
} else if (loadingComponent && !) {
return createVNode(loadingComponent);
}
};
},
Since the asynchronous component hasn't finished loading at this point, theloaded
is also false, the code will not walk into the firstif
Center.
The same components are not loaded yet and there is no error, and the code does not go to the firstelse if
Center.
If we had passed in the loading component, the code wouldn't have gone to the secondelse if
in. Because at this point in time thedelayed
value is still true, which means it is still in the delay phase. You can only wait until the previoussetTimeout
The callbacks are executed only after thedelayed
value is set to false.
moreover, as a result ofdelayed
is a ref-responsive variable, so in thesetTimeout
The callbacks in thedelayed
value will be re-rendered, i.e. the render function will be executed again. As mentioned earlier the render function here is thesetup
The code retraces its steps to the function returned in the secondelse if
Center.
this timeelse if (loadingComponent && !)
whichloadingComponent
is the loading component, andvalue is also false now. The code then goes to the
createVNode(loadingComponent)
in the page, executing this function renders the loading component to the page.
Loading Asynchronous Components
Earlier we talked about how rendering an asynchronous component executes theload
function, in which it actually executes the() => import("./")
Loading Asynchronous ComponentsWe can also see in the network panel an additional
document request.
We know.import()
The return value of the Promise is a Promise, which is triggered when the file is loaded.then()
The code will now go to the first step. At this point the code will go to the firstthen()
in it, recall the code:
const load = () => {
return loader()
.catch(() => {
// ...an omission
})
.then((comp) => {
if (
comp &&
(comp.__esModule || comp[] === "Module")
) {
comp = ;
}
resolvedComp = comp;
return comp;
});
};
existthen()
to determine if the file loaded in is an es6 module, and if so, set the module'sdefault
Export rewrite tocomp
component object. And assign the loaded-in vue component object to theresolvedComp
Variables.
After executing the firstthen()
After that the code will go to the secondthen()
in it, recall the code:
load()
.then(() => {
= true;
})
second reasonthen()
The code is simple, set theloaded
variable is set to true, which indicates that the asynchronous component has been loaded. Since theloaded
is a responsive variable, and changing its value will cause the page to be re-rendered, and the render function will be executed again. As we mentioned earlier, the render function here is thesetup
The code retraces its steps to the function returned in the secondelse if
Center.
Recap.setup
The code for the function returned in is as follows:
setup() {
// ...an omission
return () => {
if ( && resolvedComp) {
return createInnerComp(resolvedComp, instance);
} else if ( && errorComponent) {
return createVNode(errorComponent, {
error: ,
});
} else if (loadingComponent && !) {
return createVNode(loadingComponent);
}
};
},
as at this timeloaded
is true, and theresolvedComp
value for the asynchronously loaded vue component object, so the virtual DOM returned by the render function this time will be thecreateInnerComp(resolvedComp, instance)
The results of the implementation of the
createInnerComp function
Then walk the breakpoint into thecreateInnerComp
function, the simplified code in our scenario is as follows:
function createInnerComp(comp, parent) {
const { ref: ref2, props, children } = ;
const vnode = createVNode(comp, props, children);
= ref2;
return vnode;
}
createInnerComp
The function takes two arguments, the first is the vue component object to be loaded asynchronously. The second argument is the object that will be loaded asynchronously using thedefineAsyncComponent
The vue instance corresponding to the created vue component.
Then it's time to executecreateVNode
function, which you may have heard of, vue provides theh()
function is actually a call to thecreateVNode
function.
It's here with us.createVNode
The first argument the function receives is the subcomponent object, the second argument is the props to be passed to the subcomponent, and the third argument is the children to be passed to the subcomponent.createVNode
The function will generate the virtual DOM of the corresponding asynchronous component according to these three parameters, return the generated virtual DOM of the asynchronous component, and finally generate the real DOM according to the virtual DOM to render the asynchronous component to the page. The following figure (there is a summary after the figure):
summarize
This article is aboutdefineAsyncComponent
is how asynchronous components are implemented:
-
exist
defineAsyncComponent
function returns a vue component object with only thename
attributes andsetup
function. -
When rendering an asynchronous component the
setup
function in thesetup
function executes one of the built-inload
Methods. In theload
method will go ahead and execute the method defined by thedefineAsyncComponent
defined asynchronous component loading function, the return value of this loading function is a Promise, the asynchronous component loading is completed will trigger the Promise'sthen()
。 -
exist
setup
function will return a function that will be the render function of the component. -
When the asynchronous component has finished loading it will go to the previously mentioned Promise's
then()
method, in which theloaded
The value of the responsive variable is modified to true. -
Changing the value of the responsive variable causes the page to be re-rendered, and then the render function is executed. As mentioned earlier the render function at this point is the
setup
The callback function that will be returned in the function. Executing this callback function calls thecreateInnerComp
function generates the virtual DOM of the asynchronous component, and finally it is the generation of the real DOM based on the virtual DOM that renders the asynchronous subcomponent to the page.
Follow the public number: [Front-end Ouyang], give yourself a chance to advance vue
Also Ouyang wrote an open source ebookvue3 Compilation Principles Revealed, this book is accessible to middle school level front end. Totally free, just asking for a STAR.