Когда я просматривал написанный ранее проект управления фоном, я обнаружил, что использовались асинхронные компоненты Vue. И мне довелось изучать исходный код Vue раньше, и, кстати, я анализировал процесс загрузки и выполнения асинхронных компонентов.
Что такое асинхронные компоненты?
Асинхронный, в отличие от синхронного. Когда мы используем Vue, большинство компонентов, которые мы используем, являются синхронными компонентами. Во время первого рендеринга экземпляра vue был сгенерирован конструктор компонента. Асинхронный компонент должен асинхронно извлекать js соответствующего компонента через запрос, когда компонент используется (это необходимо сочетать с импортом веб-пакета). При загрузке соответствующего js получаются элементы конфигурации асинхронного компонента, тем самым создается конструктор компонента. Затем передайте forceRender, чтобы заставить экземпляр виртуальной машины, который зависит от него, повторно запустить функцию рендеринга.
Почему в то время проект выбрал асинхронные компоненты?
因为对于后台管理系统来说,通常左侧有一系列的操作tab。而每个tab对应一个组件。
当用户进入时,如果把所有的组件都进行加载,会导致用户体验下降,等待时间过长。所以就需要使用异步组件。
Когда пользователь вводит страницу, только компоненты, используемые для того, чтобы сделать его синхронизацией. Это не повлияет на работу пользовательского опыта, но и оптимизировать скорость загрузки страницы.
Vue, если есть несколько экземпляров, vm использует асинхронный компонент, асинхронный компонент будет загружен только один раз и кэширован в памяти, не повторяя загрузку.
Как используются асинхронные компоненты?
Регистрация асинхронного компонента во многом аналогична регистрации обычного компонента. Он может быть зарегистрирован как глобально, так и локально. Но разница в том, что асинхронные компоненты необходимо импортировать через функцию импорта веб-пакета. следующим образом:
// 局部注册
new Vue({
...rest,
components: {
a: () => import('./components/a.vue')
}
})
// 全局注册
Vue.component('async-comp', (resolve, reject) => ({
component: () => imort('./components/a.vue'),
loading: loadingComp,
error: errorComp,
delay: 200,
timeout: 3000
}));
Среди них асинхронные компоненты и продвинутые асинхронные компоненты. Так называемые расширенные асинхронные компоненты обеспечивают загрузку компонентов (для отображения, когда компоненты загружаются асинхронно), компоненты ошибок (для отображения, когда компоненты загружаются с ошибками), задержку (для задержки загрузки асинхронных компонентов) и тайм-аут (для настройки асинхронной загрузки компонентов). ) Максимальная продолжительность времени, если загрузка не была завершена после указанного времени, будет отображаться компонент ошибки).
Среди них компоненты ошибки и загрузки являются синхронными. Потому что при загрузке асинхронного компонента или возникновении ошибки соответствующий компонент состояния должен отображаться немедленно.
Для асинхронных компонентов, используя функцию импорта, предоставляемую веб-пакетом, веб-пакет может отдельно упаковать файлы компонентов, переданные в импорте, при упаковке. Таким образом, когда компонент используется, js компонента загружается асинхронно путем отправки запроса, а когда ресурс загружается, он передается vue для обработки загрузки.
Как выполняются асинхронные компоненты?
Прежде всего, мы должны сначала сделать описание импорта веб-пакета:
根据webpack官网的解释,import是用于动态代码拆分。它传入一个模块路径,然后返回一个promise实例。在webpack打包时,
会将import引入的模块单独打包到一个代码包中。并且在代码执行到所在行时再去加载对应的资源。
Для изучения импорта веб-пакета, пожалуйста, обратитесь кмодульный подход.
Разберем, как загружаются асинхронные компоненты. В качестве примера возьмем глобальный асинхронный компонент в приведенном выше примере:
- Vue.component добавит {'async-comp': fn } в Vue.options.components. Чтобы найти соответствующее определение компонента в createElement, когда метод vm._render выполняется при рендеринге компонента.
const ASSET_TYPES = ['component', 'directive', 'filter'];
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
// ... 略去和流程无关部分
this.options[type + 's'][id] = definition
// 执行过后,Vue.options.components['async-comp'] = fn
return definition
}
}
})
}
- Вызовите функцию createComponent в createElement, чтобы войти в процесс создания компонента vnode. В createComponent обработка асинхронных компонентов — это в основном функция resolveAsyncComponent.
// 此处的调用关系:vm.$mount -> mountComponent -> vm._update(vm._render(), hydrating) ->
// -> vm._render -> vm._c -> createElement -> _createElement -> createComponent
// 具体的流程就不具体分析了,最终会进入到createComponent函数中。
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | Array<VNode> | void {
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
// 此时 Ctor是之前定义的异步加载函数,并不是object
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// 有删减
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
// 最终会进入这里。
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
// 第一次执行渲染函数时,如果不是高级异步组件或者高级异步组件的delay不为0,则ctor默认为undefined。返回一个空的占位节点
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
)
}
}
// ... 省略其余部分
const vnode = new VNode(...opts);
return vnode;
}
-
В функции resolveAsyncComponent выполните следующие действия:
- Оцените ошибку и разрешите асинхронный компонент.Если ошибка или isloading верны или разрешены не undefined, верните компонент в соответствующее состояние.
- Все экземпляры vm, ссылающиеся на асинхронный компонент, собираются и сохраняются в factory.owners.
- Определяет функцию forceRender (используется для обхода владельцев для вызова функции рендеринга после успешной или неудачной загрузки), resolve (функция обработки после успешной загрузки асинхронного компонента), reject (функция обработки после сбоя загрузки асинхронного компонента)
- Вызовите функцию, соответствующую асинхронному компоненту, и выполните дальнейшую обработку расширенного асинхронного компонента.
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>
): Class<Component> | void {
/** 第二次进入时会命中 start */
// 如果异步组件加载失败或者超时(timeout)了,会进入这里
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
// 组件被正常加载后,会进入这里
if (isDef(factory.resolved)) {
return factory.resolved
}
// 收集引用该异步组件的vm实例,以便在forceRender时触发对应vm实例的渲染函数
const owner = currentRenderingInstance
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner)
}
// 如果处于loading状态,返回loadingComp。
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
/** 第二次进入时会命中 end */
if (owner && !isDef(factory.owners)) {
const owners = factory.owners = [owner]
let sync = true
let timerLoading = null
let timerTimeout = null
// 当vm实例销毁时,将factory.owners中当前监听的owner移除。
;(owner: any).$on('hook:destroyed', () => remove(owners, owner))
const forceRender = (renderCompleted: boolean) => {
for (let i = 0, l = owners.length; i < l; i++) {
// 重新执行了vm渲染watcher的update方法,即vm._update(vm._render(), hytrating)
(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>) => {
// 缓存并生成加载回来的异步组件构造器
factory.resolved = ensureCtor(res, baseCtor)
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
// 当异步组件加载完成时,此时sync为false。执行forceRender触发重新渲染,再次进入该函数后命中factory.resolved分支。
if (!sync) {
forceRender(true)
} else {
owners.length = 0
}
})
const reject = once(reason => {
process.env.NODE_ENV !== 'production' && warn(
`Failed to resolve async component: ${String(factory)}` +
(reason ? `\nReason: ${reason}` : '')
)
// 加载异步组件失败的情况
if (isDef(factory.errorComp)) {
factory.error = true
forceRender(true)
}
})
// 对于高级异步组件,会返回一个object,如示例用法中全局注册组件。
const res = factory(resolve, reject)
if (isObject(res)) {
// 对于上述用法中 局部注册组件那样, import会返回一个promise实例。
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isPromise(res.component)) {
// 高级异步组件。例如实例中配置了 component: () => import('./a.vue')
res.component.then(resolve, reject)
// 缓存错误组件构造器
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor)
}
// 缓存loading组件构造器
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor)
if (res.delay === 0) {
// 只有delay配置为0时,第一次执行渲染时才会返回loading组件构造器
factory.loading = true
} else {
// 如果设置了delay,
timerLoading = setTimeout(() => {
timerLoading = null
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender(false)
}
}, res.delay || 200)
}
}
if (isDef(res.timeout)) {
// 如果异步组件加载时间超过了设置的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
}
Таким образом, загрузка и выполнение асинхронных компонентов фактически достигается за счет двойного выполнения функции рендеринга.
Когда он выполняется в первый раз, обрабатываются функции, соответствующие асинхронным компонентам, определяются компоненты для обработки состояний загрузки и ошибок, а разрешение и отклонение передаются на фабрику. Когда файл асинхронного компонента загружается и соответственно обрабатывается с помощью функции разрешения или отклонения, состояние ошибки или разрешения фабрики изменится. После установки factory.error или factory.resolved будет вызываться forceRender для запуска обновления наблюдателя рендеринга, соответствующего экземпляру vm, повторного выполнения рендеринга, а затем выполнения второго рендеринга.
При рендеринге во второй раз он также войдет в метод resolveAsyncComponent. В это время результат загрузки асинхронного компонента определен, и будет возвращен соответствующий компонент в успешном состоянии (то есть определенный нами асинхронный компонент) или компонент в состоянии сбоя. После этого выполняется следующий процесс.
Исследование асинхронных компонентов здесь впервые...