type Flatten<T> = { [K in keyof T]: T[K] }
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> {
inheritAttrs?: boolean
}
+export type ComponentType = ComponentClass | FunctionalComponent
+
// this interface is merged with the class type
// to represent a mounted component
export interface MountedComponent<D = Data, P = Data>
-import { Slots } from './vdom'
+import { Slots, VNodeData } from './vdom'
import { MountedComponent } from './component'
export type Data = Record<string, any>
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> {
const render = tag as FunctionalComponent
const { props, attrs } = resolveProps(data, render.props, render)
const subTree = (vnode.children = normalizeComponentRoot(
- render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ),
+ render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ, data),
vnode,
attrs,
render.inheritAttrs
if (shouldUpdate) {
const { props, attrs } = resolveProps(nextData, render.props, render)
const nextTree = (nextVNode.children = normalizeComponentRoot(
- render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ),
+ render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ, nextData),
nextVNode,
attrs,
render.inheritAttrs
// these are imported on-demand and can be tree-shaken
export * from './optional/directive'
export * from './optional/context'
+export * from './optional/asyncComponent'
// flags & types
-export { ComponentClass, FunctionalComponent } from './component'
+export { ComponentType, ComponentClass, FunctionalComponent } from './component'
export { ComponentOptions, PropType } from './componentOptions'
export { VNodeFlags, ChildrenFlags } from './flags'
export { VNode, VNodeData, VNodeChildren, Key, Ref, Slots, Slot } from './vdom'
+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
+ )
+}
import { Component } from '../component'
import { Slots } from '../vdom'
-const contextStore = observable() as Record<string, any>
+const contextStore = observable() as Record<string | symbol, any>
export class Provide extends Component {
updateValue() {
Provide.options = {
props: {
id: {
- type: String,
+ type: [String, Symbol],
required: true
},
value: {