diff --git a/packages/core/src/componentOptions.ts b/packages/core/src/componentOptions.ts index d12153a8..fbe25083 100644 --- a/packages/core/src/componentOptions.ts +++ b/packages/core/src/componentOptions.ts @@ -1,10 +1,10 @@ -import { Slots, VNodeData } from './vdom' +import { Slots } from './vdom' import { MountedComponent } from './component' export type Data = Record export interface RenderFunction

{ - (props: P, slots: Slots, attrs: Data, rawData: VNodeData | null): any + (props: P, slots: Slots, attrs: Data): any } export interface ComponentOptions { diff --git a/packages/core/src/createRenderer.ts b/packages/core/src/createRenderer.ts index c129777b..6e8a09a8 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, data), + render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ), 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, nextData), + render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ), nextVNode, attrs, render.inheritAttrs @@ -1167,6 +1167,10 @@ export function createRenderer(options: RendererOptions) { (__COMPAT__ && (parentVNode.children as MountedComponent)) || createComponentInstance(parentVNode, Component, parentComponent) + if (instance.beforeMount) { + instance.beforeMount.call(instance.$proxy) + } + const queueUpdate = (instance.$forceUpdate = () => { queueJob(instance._updateHandle, flushHooks) }) @@ -1206,9 +1210,6 @@ export function createRenderer(options: RendererOptions) { instance: MountedComponent, ref: Ref | null ) { - if (instance.beforeMount) { - instance.beforeMount.call(instance.$proxy) - } if (ref) { mountRef(ref, instance) } diff --git a/packages/core/src/optional/asyncComponent.ts b/packages/core/src/optional/asyncComponent.ts index 42a7ec22..752035a3 100644 --- a/packages/core/src/optional/asyncComponent.ts +++ b/packages/core/src/optional/asyncComponent.ts @@ -1,6 +1,6 @@ import { ChildrenFlags } from '../flags' -import { createComponentVNode, VNodeData } from '../vdom' -import { Component, ComponentType, FunctionalComponent } from '../component' +import { createComponentVNode, Slots } from '../vdom' +import { Component, ComponentType, ComponentClass } from '../component' export interface AsyncComponentFactory { (): Promise @@ -22,92 +22,90 @@ export type AsyncComponentOptions = interface AsyncContainerData { comp: ComponentType | null err: Error | null + delayed: boolean 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 { +): ComponentClass { if (typeof options === 'function') { options = { factory: options } } - return (_, __, ___, rawData) => - createComponentVNode( - AsyncContainer, - { options, rawData }, - null, - ChildrenFlags.NO_CHILDREN - ) + + const { + factory, + timeout, + delay = 200, + loading: loadingComp, + error: errorComp + } = options + + return class AsyncContainer extends Component { + data() { + return { + comp: null, + err: null, + delayed: false, + timedOut: false + } + } + + // doing this in beforeMount so this is non-SSR only + beforeMount() { + 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) + } + if (delay != null) { + this.delayed = true + setTimeout(() => { + this.delayed = false + }, delay) + } + } + + render(props: any, slots: Slots) { + if (this.err || (this.timedOut && !this.comp)) { + const error = + this.err || new Error(`Async component timed out after ${timeout}ms.`) + return errorComp + ? createComponentVNode( + errorComp, + { error }, + null, + ChildrenFlags.NO_CHILDREN + ) + : null + } else if (this.comp) { + return createComponentVNode( + this.comp, + props, + slots, + ChildrenFlags.STABLE_SLOTS + ) + } else { + return loadingComp && !this.delayed + ? createComponentVNode( + loadingComp, + null, + null, + ChildrenFlags.NO_CHILDREN + ) + : null + } + } + } as ComponentClass } diff --git a/packages/core/src/vdom.ts b/packages/core/src/vdom.ts index 72a71c45..578cff95 100644 --- a/packages/core/src/vdom.ts +++ b/packages/core/src/vdom.ts @@ -123,7 +123,7 @@ export function createElementVNode( export function createComponentVNode( comp: any, data: VNodeData | null, - children: VNodeChildren, + children: VNodeChildren | Slots, childFlags: ChildrenFlags, key?: Key | null, ref?: Ref | null @@ -169,7 +169,9 @@ export function createComponentVNode( // slots let slots: any - if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) { + if (childFlags === ChildrenFlags.STABLE_SLOTS) { + slots = children + } else if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) { childFlags = children ? ChildrenFlags.DYNAMIC_SLOTS : ChildrenFlags.NO_CHILDREN @@ -361,9 +363,12 @@ export function normalizeVNodes( // ensure all slot functions return Arrays function normalizeSlots(slots: { [name: string]: any }): Slots { - const normalized: Slots = {} + if (slots._normalized) { + return slots + } + const normalized = { _normalized: true } as any for (const name in slots) { - normalized[name] = (...args) => normalizeSlot(slots[name](...args)) + normalized[name] = (...args: any[]) => normalizeSlot(slots[name](...args)) } return normalized }