async component improvements

This commit is contained in:
Evan You 2017-04-04 15:06:00 +08:00
parent 354c2f4922
commit 4e980976ea
2 changed files with 77 additions and 34 deletions

View File

@ -1,62 +1,101 @@
/* @flow */ /* @flow */
// () => ({
// component: import('./xxx.vue'),
// delay: 200,
// loading: LoadingComponent,
// error: ErrorComponent
// })
import { import {
warn, warn,
isObject once,
isDef,
isUndef,
isTrue,
isObject,
isFunction
} from 'core/util/index' } from 'core/util/index'
function ensureCtor (comp, base) {
return isObject(comp)
? base.extend(comp)
: comp
}
export function resolveAsyncComponent ( export function resolveAsyncComponent (
factory: Function, factory: Function,
baseCtor: Class<Component>, baseCtor: Class<Component>,
context: Component context: Component
): Class<Component> | void { ): Class<Component> | void {
if (factory.resolved) { if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
}
if (isDef(factory.resolved)) {
return factory.resolved return factory.resolved
} }
const cb = () => context.$forceUpdate() if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
if (factory.requested) { return factory.loadingComp
// pool callbacks }
factory.pendingCallbacks.push(cb)
if (isDef(factory.contexts)) {
// already pending
factory.contexts.push(context)
} else { } else {
factory.requested = true const contexts = factory.contexts = [context]
const cbs = factory.pendingCallbacks = [cb]
let sync = true let sync = true
const resolve = (res: Object | Class<Component>) => { const forceRender = () => {
if (isObject(res)) { for (let i = 0, l = contexts.length; i < l; i++) {
res = baseCtor.extend(res) contexts[i].$forceUpdate()
}
// cache resolved
factory.resolved = res
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if (!sync) {
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i](res)
}
} }
} }
const reject = reason => { 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()
}
})
const reject = once(reason => {
process.env.NODE_ENV !== 'production' && warn( process.env.NODE_ENV !== 'production' && warn(
`Failed to resolve async component: ${String(factory)}` + `Failed to resolve async component: ${String(factory)}` +
(reason ? `\nReason: ${reason}` : '') (reason ? `\nReason: ${reason}` : '')
) )
} if (isDef(factory.errorComp)) {
factory.error = true
forceRender()
}
})
const res = factory(resolve, reject) const res = factory(resolve, reject)
// handle promise if (isObject(res)) {
if (res && typeof res.then === 'function' && !factory.resolved) { if (isFunction(res.then)) {
res.then(resolve, reject) // () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isDef(res.component) && isFunction(res.component.then)) {
if (isDef(res.error)) {
factory.errorComp = ensureCtor(res.error, baseCtor)
}
if (isDef(res.loading)) {
factory.loadingComp = ensureCtor(res.loading, baseCtor)
setTimeout(() => {
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender()
}
}, res.delay || 200)
}
if (isDef(res.timeout)) {
setTimeout(reject, res.timeout)
}
res.component.then(resolve, reject)
}
} }
sync = false sync = false

View File

@ -14,6 +14,10 @@ export function isTrue (v: any): boolean {
return v === true return v === true
} }
export function isFunction (v: any): boolean {
return typeof v === 'function'
}
/** /**
* Convert a value to a string that is actually rendered. * Convert a value to a string that is actually rendered.
*/ */
@ -250,10 +254,10 @@ export function looseIndexOf (arr: Array<mixed>, val: mixed): number {
*/ */
export function once (fn: Function): Function { export function once (fn: Function): Function {
let called = false let called = false
return () => { return function () {
if (!called) { if (!called) {
called = true called = true
fn() fn.apply(this, arguments)
} }
} }
} }