diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 832201fc..e1667b10 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -15,7 +15,7 @@ import { ErrorTypes } from './errorHandling' type Flatten = { [K in keyof T]: T[K] } export interface ComponentClass extends Flatten { - new (): MountedComponent & D & P + new (): D & P & MountedComponent } export interface FunctionalComponent

extends RenderFunction

{ @@ -24,6 +24,8 @@ export interface FunctionalComponent

extends RenderFunction

{ inheritAttrs?: boolean } +export type ComponentType = ComponentClass | FunctionalComponent + // this interface is merged with the class type // to represent a mounted component export interface MountedComponent diff --git a/packages/core/src/componentOptions.ts b/packages/core/src/componentOptions.ts index fbe25083..d12153a8 100644 --- a/packages/core/src/componentOptions.ts +++ b/packages/core/src/componentOptions.ts @@ -1,10 +1,10 @@ -import { Slots } from './vdom' +import { Slots, VNodeData } from './vdom' import { MountedComponent } from './component' export type Data = Record export interface RenderFunction

{ - (props: P, slots: Slots, attrs: Data): any + (props: P, slots: Slots, attrs: Data, rawData: VNodeData | null): any } export interface ComponentOptions { diff --git a/packages/core/src/createRenderer.ts b/packages/core/src/createRenderer.ts index 7ddd730d..c129777b 100644 --- a/packages/core/src/createRenderer.ts +++ b/packages/core/src/createRenderer.ts @@ -284,7 +284,7 @@ export function createRenderer(options: RendererOptions) { 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 @@ -581,7 +581,7 @@ export function createRenderer(options: RendererOptions) { 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 diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index aee529bd..fdafe97c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -17,9 +17,10 @@ export { createComponentInstance } from './componentUtils' // 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' diff --git a/packages/core/src/optional/asyncComponent.ts b/packages/core/src/optional/asyncComponent.ts index e69de29b..42a7ec22 100644 --- a/packages/core/src/optional/asyncComponent.ts +++ b/packages/core/src/optional/asyncComponent.ts @@ -0,0 +1,113 @@ +import { ChildrenFlags } from '../flags' +import { createComponentVNode, VNodeData } from '../vdom' +import { Component, ComponentType, FunctionalComponent } from '../component' + +export interface AsyncComponentFactory { + (): Promise + 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 + ) +} diff --git a/packages/core/src/optional/context.ts b/packages/core/src/optional/context.ts index 7b742503..b7a5adc6 100644 --- a/packages/core/src/optional/context.ts +++ b/packages/core/src/optional/context.ts @@ -2,7 +2,7 @@ import { observable } from '@vue/observer' import { Component } from '../component' import { Slots } from '../vdom' -const contextStore = observable() as Record +const contextStore = observable() as Record export class Provide extends Component { updateValue() { @@ -40,7 +40,7 @@ if (__DEV__) { Provide.options = { props: { id: { - type: String, + type: [String, Symbol], required: true }, value: {