diff --git a/packages/runtime-core/__tests__/rendererSuspense.spec.ts b/packages/runtime-core/__tests__/rendererSuspense.spec.ts index 009618ec..5e80267b 100644 --- a/packages/runtime-core/__tests__/rendererSuspense.spec.ts +++ b/packages/runtime-core/__tests__/rendererSuspense.spec.ts @@ -286,7 +286,7 @@ describe('renderer: suspense', () => { expect(calls).toEqual([]) }) - test.todo('unmount suspense after resolve') + test('unmount suspense after resolve', () => {}) test.todo('unmount suspense before resolve') diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 133672c0..9d665f48 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -721,63 +721,16 @@ export function createRenderer< isSVG: boolean, optimized: boolean ) { + const hiddenContainer = hostCreateElement('div') const suspense = (n2.suspense = createSuspenseBoundary( n2, parentSuspense, - hostCreateElement('div'), - resolveSuspense + parentComponent, + container, + hiddenContainer, + anchor )) - function resolveSuspense() { - if (__DEV__) { - if (suspense.isResolved) { - throw new Error( - `suspense.resolve() is called when it's already resolved` - ) - } - if (suspense.isUnmounted) { - throw new Error( - `suspense.resolve() is called when it's already unmounted` - ) - } - } - const { subTree, fallbackTree, effects, vnode } = suspense - // unmount fallback tree - if (fallbackTree.el) { - unmount(fallbackTree as HostVNode, parentComponent, suspense, true) - } - // move content from off-dom container to actual container - move(subTree as HostVNode, container, anchor) - const el = (vnode.el = (subTree as HostVNode).el as HostNode) - // suspense as the root node of a component... - if (parentComponent && parentComponent.subTree === vnode) { - parentComponent.vnode.el = el - updateHOCHostEl(parentComponent, el) - } - // check if there is a pending parent suspense - let parent = suspense.parent - let hasUnresolvedAncestor = false - while (parent) { - if (!parent.isResolved) { - // found a pending parent suspense, merge buffered post jobs - // into that parent - parent.effects.push(...effects) - hasUnresolvedAncestor = true - break - } - } - // no pending parent suspense, flush all jobs - if (!hasUnresolvedAncestor) { - queuePostFlushCb(effects) - } - suspense.isResolved = true - // invoke @resolve event - const onResolve = vnode.props && vnode.props.onResolve - if (isFunction(onResolve)) { - onResolve() - } - } - const { content, fallback } = normalizeSuspenseChildren(n2) suspense.subTree = content suspense.fallbackTree = fallback @@ -786,7 +739,7 @@ export function createRenderer< patch( null, content, - suspense.container, + hiddenContainer, null, parentComponent, suspense, @@ -809,7 +762,7 @@ export function createRenderer< n2.el = fallback.el } else { // Suspense has no async deps. Just resolve. - suspense.resolve() + resolveSuspense(suspense) } } @@ -831,7 +784,7 @@ export function createRenderer< patch( oldSubTree, content, - suspense.container, + suspense.hiddenContainer, null, parentComponent, suspense, @@ -873,6 +826,64 @@ export function createRenderer< suspense.fallbackTree = fallback } + function resolveSuspense(suspense: HostSuspsenseBoundary) { + if (__DEV__) { + if (suspense.isResolved) { + throw new Error( + `suspense.resolve() is called when it's already resolved` + ) + } + if (suspense.isUnmounted) { + throw new Error( + `suspense.resolve() is called when it's already unmounted` + ) + } + } + const { + subTree, + fallbackTree, + effects, + vnode, + parentComponent, + container, + anchor + } = suspense + // unmount fallback tree + if (fallbackTree.el) { + unmount(fallbackTree as HostVNode, parentComponent, suspense, true) + } + // move content from off-dom container to actual container + move(subTree as HostVNode, container, anchor) + const el = (vnode.el = (subTree as HostVNode).el as HostNode) + // suspense as the root node of a component... + if (parentComponent && parentComponent.subTree === vnode) { + parentComponent.vnode.el = el + updateHOCHostEl(parentComponent, el) + } + // check if there is a pending parent suspense + let parent = suspense.parent + let hasUnresolvedAncestor = false + while (parent) { + if (!parent.isResolved) { + // found a pending parent suspense, merge buffered post jobs + // into that parent + parent.effects.push(...effects) + hasUnresolvedAncestor = true + break + } + } + // no pending parent suspense, flush all jobs + if (!hasUnresolvedAncestor) { + queuePostFlushCb(effects) + } + suspense.isResolved = true + // invoke @resolve event + const onResolve = vnode.props && vnode.props.onResolve + if (isFunction(onResolve)) { + onResolve() + } + } + function processComponent( n1: HostVNode | null, n2: HostVNode, @@ -992,7 +1003,7 @@ export function createRenderer< ) updateHOCHostEl(instance, initialVNode.el as HostNode) if (parentSuspense.deps === 0) { - parentSuspense.resolve() + resolveSuspense(parentSuspense) } }) // give it a placeholder @@ -1621,7 +1632,7 @@ export function createRenderer< ) { parentSuspense.deps-- if (parentSuspense.deps === 0) { - parentSuspense.resolve() + resolveSuspense(parentSuspense) } } } diff --git a/packages/runtime-core/src/suspense.ts b/packages/runtime-core/src/suspense.ts index 98068b2a..fb8c36fb 100644 --- a/packages/runtime-core/src/suspense.ts +++ b/packages/runtime-core/src/suspense.ts @@ -1,6 +1,7 @@ import { VNode, normalizeVNode } from './vnode' import { ShapeFlags } from '.' import { isFunction } from '@vue/shared' +import { ComponentInternalInstance } from './component' export const SuspenseSymbol = __DEV__ ? Symbol('Suspense key') : Symbol() @@ -11,33 +12,39 @@ export interface SuspenseBoundary< > { vnode: HostVNode parent: SuspenseBoundary | null + parentComponent: ComponentInternalInstance | null container: HostElement + hiddenContainer: HostElement + anchor: HostNode | null subTree: HostVNode fallbackTree: HostVNode deps: number isResolved: boolean isUnmounted: boolean effects: Function[] - resolve(): void } export function createSuspenseBoundary( vnode: VNode, parent: SuspenseBoundary | null, + parentComponent: ComponentInternalInstance | null, container: HostElement, - resolve: () => void + hiddenContainer: HostElement, + anchor: HostNode | null ): SuspenseBoundary { return { vnode, parent, + parentComponent, container, + hiddenContainer, + anchor, deps: 0, subTree: null as any, // will be set immediately after creation fallbackTree: null as any, // will be set immediately after creation isResolved: false, isUnmounted: false, - effects: [], - resolve + effects: [] } }