feat: async component

This commit is contained in:
Evan You 2018-09-25 21:28:52 -04:00
parent 1def00e96e
commit 2c753388c3
6 changed files with 124 additions and 8 deletions

View File

@ -15,7 +15,7 @@ import { ErrorTypes } from './errorHandling'
type Flatten<T> = { [K in keyof T]: T[K] } type Flatten<T> = { [K in keyof T]: T[K] }
export interface ComponentClass extends Flatten<typeof InternalComponent> { export interface ComponentClass extends Flatten<typeof InternalComponent> {
new <D = Data, P = Data>(): MountedComponent<D, P> & D & P new <D = Data, P = Data>(): D & P & MountedComponent<D, P>
} }
export interface FunctionalComponent<P = Data> extends RenderFunction<P> { export interface FunctionalComponent<P = Data> extends RenderFunction<P> {
@ -24,6 +24,8 @@ export interface FunctionalComponent<P = Data> extends RenderFunction<P> {
inheritAttrs?: boolean inheritAttrs?: boolean
} }
export type ComponentType = ComponentClass | FunctionalComponent
// this interface is merged with the class type // this interface is merged with the class type
// to represent a mounted component // to represent a mounted component
export interface MountedComponent<D = Data, P = Data> export interface MountedComponent<D = Data, P = Data>

View File

@ -1,10 +1,10 @@
import { Slots } from './vdom' import { Slots, VNodeData } from './vdom'
import { MountedComponent } from './component' import { MountedComponent } from './component'
export type Data = Record<string, any> export type Data = Record<string, any>
export interface RenderFunction<P = Data> { export interface RenderFunction<P = Data> {
(props: P, slots: Slots, attrs: Data): any (props: P, slots: Slots, attrs: Data, rawData: VNodeData | null): any
} }
export interface ComponentOptions<D = Data, P = Data> { export interface ComponentOptions<D = Data, P = Data> {

View File

@ -284,7 +284,7 @@ export function createRenderer(options: RendererOptions) {
const render = tag as FunctionalComponent const render = tag as FunctionalComponent
const { props, attrs } = resolveProps(data, render.props, render) const { props, attrs } = resolveProps(data, render.props, render)
const subTree = (vnode.children = normalizeComponentRoot( const subTree = (vnode.children = normalizeComponentRoot(
render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ), render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ, data),
vnode, vnode,
attrs, attrs,
render.inheritAttrs render.inheritAttrs
@ -581,7 +581,7 @@ export function createRenderer(options: RendererOptions) {
if (shouldUpdate) { if (shouldUpdate) {
const { props, attrs } = resolveProps(nextData, render.props, render) const { props, attrs } = resolveProps(nextData, render.props, render)
const nextTree = (nextVNode.children = normalizeComponentRoot( const nextTree = (nextVNode.children = normalizeComponentRoot(
render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ), render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ, nextData),
nextVNode, nextVNode,
attrs, attrs,
render.inheritAttrs render.inheritAttrs

View File

@ -17,9 +17,10 @@ export { createComponentInstance } from './componentUtils'
// these are imported on-demand and can be tree-shaken // these are imported on-demand and can be tree-shaken
export * from './optional/directive' export * from './optional/directive'
export * from './optional/context' export * from './optional/context'
export * from './optional/asyncComponent'
// flags & types // flags & types
export { ComponentClass, FunctionalComponent } from './component' export { ComponentType, ComponentClass, FunctionalComponent } from './component'
export { ComponentOptions, PropType } from './componentOptions' export { ComponentOptions, PropType } from './componentOptions'
export { VNodeFlags, ChildrenFlags } from './flags' export { VNodeFlags, ChildrenFlags } from './flags'
export { VNode, VNodeData, VNodeChildren, Key, Ref, Slots, Slot } from './vdom' export { VNode, VNodeData, VNodeChildren, Key, Ref, Slots, Slot } from './vdom'

View File

@ -0,0 +1,113 @@
import { ChildrenFlags } from '../flags'
import { createComponentVNode, VNodeData } from '../vdom'
import { Component, ComponentType, FunctionalComponent } from '../component'
export interface AsyncComponentFactory {
(): Promise<ComponentType>
resolved?: ComponentType
}
export interface AsyncComponentFullOptions {
factory: AsyncComponentFactory
loading?: ComponentType
error?: ComponentType
delay?: number
timeout?: number
}
export type AsyncComponentOptions =
| AsyncComponentFactory
| AsyncComponentFullOptions
interface AsyncContainerData {
comp: ComponentType | null
err: Error | null
timedOut: boolean
}
interface AsyncContainerProps {
options: AsyncComponentFullOptions
rawData: VNodeData | null
}
export class AsyncContainer extends Component<
AsyncContainerData,
AsyncContainerProps
> {
data() {
return {
comp: null,
err: null,
timedOut: false
}
}
created() {
const { factory, timeout } = this.$props.options
if (factory.resolved) {
this.comp = factory.resolved
} else {
factory()
.then(resolved => {
this.comp = factory.resolved = resolved
})
.catch(err => {
this.err = err
})
}
if (timeout != null) {
setTimeout(() => {
this.timedOut = true
}, timeout)
}
}
render(props: AsyncContainerProps) {
if (this.err || (this.timedOut && !this.comp)) {
const error =
this.err ||
new Error(`Async component timed out after ${props.options.timeout}ms.`)
const errorComp = props.options.error
return errorComp
? createComponentVNode(
errorComp,
{ error },
null,
ChildrenFlags.NO_CHILDREN
)
: null
} else if (this.comp) {
return createComponentVNode(
this.comp,
props.rawData,
null,
ChildrenFlags.UNKNOWN_CHILDREN
)
} else {
const loadingComp = props.options.loading
return loadingComp
? createComponentVNode(
loadingComp,
null,
null,
ChildrenFlags.NO_CHILDREN
)
: null
}
}
}
export function createAsyncComponent(
options: AsyncComponentOptions
): FunctionalComponent {
if (typeof options === 'function') {
options = { factory: options }
}
return (_, __, ___, rawData) =>
createComponentVNode(
AsyncContainer,
{ options, rawData },
null,
ChildrenFlags.NO_CHILDREN
)
}

View File

@ -2,7 +2,7 @@ import { observable } from '@vue/observer'
import { Component } from '../component' import { Component } from '../component'
import { Slots } from '../vdom' import { Slots } from '../vdom'
const contextStore = observable() as Record<string, any> const contextStore = observable() as Record<string | symbol, any>
export class Provide extends Component { export class Provide extends Component {
updateValue() { updateValue() {
@ -40,7 +40,7 @@ if (__DEV__) {
Provide.options = { Provide.options = {
props: { props: {
id: { id: {
type: String, type: [String, Symbol],
required: true required: true
}, },
value: { value: {