]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: async component
authorEvan You <yyx990803@gmail.com>
Wed, 26 Sep 2018 01:28:52 +0000 (21:28 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 26 Sep 2018 01:28:52 +0000 (21:28 -0400)
packages/core/src/component.ts
packages/core/src/componentOptions.ts
packages/core/src/createRenderer.ts
packages/core/src/index.ts
packages/core/src/optional/asyncComponent.ts
packages/core/src/optional/context.ts

index 832201fce5782d0491bbd03f70f979ee968ac3d1..e1667b10e667211de7a861e36ebba4ea2aaadbda 100644 (file)
@@ -15,7 +15,7 @@ import { ErrorTypes } from './errorHandling'
 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> {
@@ -24,6 +24,8 @@ 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>
index fbe25083935032028ded0efd4ad39cc09049476f..d12153a84f9ea35f05ef13a7f76132913ac5875a 100644 (file)
@@ -1,10 +1,10 @@
-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> {
index 7ddd730df5f5947a6981083749d9454ba84b577e..c129777b89d9edf83de15f76d31d665cf5e8a747 100644 (file)
@@ -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
index aee529bd5ff757bef512af966deac59b8210643b..fdafe97c37950b7beac8cb70158463ef3971218f 100644 (file)
@@ -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'
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..42a7ec22a577d2756f5200e2fab743bf626e0b5f 100644 (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
+    )
+}
index 7b742503744c06f4818142b88ffd94214722d5b6..b7a5adc6d210b2adc215024791376a4c603e179c 100644 (file)
@@ -2,7 +2,7 @@ import { observable } from '@vue/observer'
 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() {
@@ -40,7 +40,7 @@ if (__DEV__) {
   Provide.options = {
     props: {
       id: {
-        type: String,
+        type: [String, Symbol],
         required: true
       },
       value: {