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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user