Asynchronous components
Before viewing this article, it is a good idea to be familiar with component instantiation and mounting. You can read my last post if you don’t
Vue component analysis
With components out of the way, it’s time to look at asynchronous components. Asynchronous components are important in project development. In combination with code-spliting of Webpack, the js packed by components can be loaded asynchronously to optimize browser loading and improve page rendering speed. Since it works with WebPack, we need to create a basic project using vue-CLI, so let’s look at an example first
Vue.component('HelloWorld'.function (resolve) {
// This particular 'require' syntax will be told to Webpack
// Automatically splits your build code into multiple packages
// Will be loaded via promise
require(['./components/HelloWorld.vue'], resolve)
})
new Vue({
render: h= > h(App)
}).$mount('#app')
Copy the code
We remove the local registration of the App component in the demo, and then global registration. Here is the example code.
With the example ready, we need to think about where the entrance is. When we open package-json, we see that it is started by vue-cli-service, so obviously we need to find the startup file. In node_modules, we look at the.bin directory, where we can find the vue-cli-service script file. Here we can see that its execution directory is @vue/cli-service/bin/vue-cli-service.js. The process is not described in detail, let’s directly look at the code in config/base.js
webpackConfig.resolve
.alias
.set(
'vue$',
options.runtimeCompiler
? 'vue/dist/vue.esm.js'
: 'vue/dist/vue.runtime.esm.js'
)
Copy the code
It’s very simple if we turn on the runtimeCompiler option it brings in vue.esm.js, otherwise it brings in vue.Runtime.esm.js. The difference is the presence or absence of compiler.
Now that the imported VUE source code is there, let’s think about where the entry for the asynchronous component is. As we learned in the previous article, creating asynchronous components uses the createComponent method, which is in the vDOM/create-Component file, Obviously now we can just write debugger at the beginning of createComponent and go into debug mode in development state
Initialize the
In this case, when we register globally, the second argument is a method, not an object, so we execute initAssetRegisters when type=component
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
Copy the code
If isPlainObject is false, vue will not execute the code inside. Then we look at the createComponent method.
// Create a child component
export function createComponent (
Ctor: Class<Component> | Function | Object | void, data: ? VNodeData, context: Component, children: ?Array<VNode>, tag? : string) :VNode | Array<VNode> | void {
// Asynchronous components
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
// The component constructs the factory function Vue
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
return vnode
}
Copy the code
You can see that Ctor we passed in function, and we didn’t extend. So it doesn’t have CID, which is the statement that goes into it. With the entry parsed, let’s start with resolveAsyncComponent.
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
) :Class<Component> | void {
const owner = currentRenderingInstance
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) = > {}
const resolve = once((res: Object | Class<Component>) = > {})
// use a callback function
const reject = once(reason= >{})const res = factory(resolve, reject)
sync = false
return factory.loading
? factory.loadingComp
: factory.resolved
}
}
Copy the code
CurrentRenderingInstance Gets the current render instance, which is the App instance. This method is a long one, so let’s delete this example and just look at the core. The first time you use factory.owners will not exist. So it goes into if.
We first declare a number of attributes, mainly resolve and reject, and they are executed only once. When we execute factory(resolve, reject), we’re actually executing the definition function, which means we execute it
require(['./components/HelloWorld.vue'], resolve)
Copy the code
Require is webpack’s method, so we’ll go into Webpack and do it. By using the resolve and reject functions above, we can guess that require must be a new Promise.
Exactly. The code in the Webpack, which is new Promise, then creates a script on the fly. Then go back to the process to continue. In a long if statement, it’s actually not executing now because there’s no return value, and the current RES is empty. So the last two pieces of code are now executed. Sync = false and return factory.resolved
While the current factory.resolved is undefined, look at the createComponent method
if (Ctor === undefined) {
return createAsyncPlaceholder(asyncFactory, data, context, children, tag)
}
Copy the code
So you go into createAsyncPlaceholder, and the name is creating an asynchronous Placeholder. Let’s look at the code
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
Very simply, vue creates an empty Vnode and assigns parameters to vNode. After executing the INSERT node we can see that the element page of the console is
<div id="app">
<img alt="Vue logo" src="/img/logo.82b9c7a5.png">
<! ---->
</div>
Copy the code
Look at the presence of a comment node in the component area. And in network we can see an empty 0.js. This completes the initialization process
Perform the resolve
Here we make a breakpoint inside the resolve function and look at the call stack. There’s a big Promise. Then async, and then resolve is executed.
const resolve = once((res: Object | Class<Component>) = > {
factory.resolved = ensureCtor(res, baseCtor)
if(! sync) { forceRender(true)}else {
owners.length = 0}})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 method is simple
- perform
ensureCtor
The method is executed by taking the component objectVue.extend
Initialize the child component constructor. - The current
sync
isfalse
. So we will executeforceRender(true)
ForceRender is very simple. Let’s look at the code
var forceRender = function (renderCompleted) {
for (var i = 0, l = owners.length; i < l; i++) {
owners[i].$forceUpdate()
}
}
Copy the code
Here we have the owners[I] saved by the closure, which currently has only one App instance. This.$forceUpdate is executed. Re-run watcher.update to update the page. At this point we can get the child component instance, which goes through the normal createComponent process and renders it to the page
Other examples
Generally we do not use the above method, it has a better writing method based on ES2015
Vue.component('HelloWorld'.// The import function returns a Promise object
() = > import(/* webpackChunkName: "HelloWorld" */'./components/HelloWorld.vue'))Copy the code
The important thing to note in this example is that we have a return value, which is returned by WebPack, which is a Promise. Since there is a return value, in the following code
const res = factory(resolve, reject)
// If it's a promise
if (isObject(res)) {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
}
}
Copy the code
Obviously we run res.then, which executes to resolve, and the code is the same after that.
There is another example in the official documentation
// The third way is advanced asynchronous components
const AsyncComp = () = > ({
// Components to load. It should be a Promise
component: import('./components/HelloWorld.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
})
const LoadingComp = {
template: '<div>loading</div>'
}
const ErrorComp = {
template: '<div>error</div>'
}
Vue.component('HelloWorld', AsyncComp)
Copy the code
This syntax is new, and the execution of the code is really just a matter of determining how well the promise performs. In the source code is also very simple, I believe that can see.
conclusion
If an interviewer asks: How does vUE’s asynchronous component work?
An asynchronous component, as its name implies, initializes the child component constructor when a Promise created by Webpack is executed to THEN, and then re-renders it by executing $foreUpdate of the current instance, the parent instance.