feat: async component
This commit is contained in:
parent
1def00e96e
commit
2c753388c3
@ -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>
|
||||||
|
@ -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> {
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
@ -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: {
|
||||||
|
Loading…
Reference in New Issue
Block a user