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.