From 73aea436bb91a26d2d0de65157e4c81d1770288a Mon Sep 17 00:00:00 2001 From: edison Date: Sun, 25 Feb 2024 20:22:12 +0800 Subject: [PATCH] fix(suspense): handle suspense switching with nested suspense (#10184) close #10098 --- .../__tests__/components/Suspense.spec.ts | 95 ++++++++++++++++++- .../runtime-core/src/components/Suspense.ts | 4 +- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 928da872faf..fd1913b2c9c 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -22,7 +22,7 @@ import { watch, watchEffect, } from '@vue/runtime-test' -import { createApp, defineComponent } from 'vue' +import { computed, createApp, defineComponent, inject, provide } from 'vue' import type { RawSlots } from 'packages/runtime-core/src/componentSlots' import { resetSuspenseId } from '../../src/components/Suspense' @@ -1039,6 +1039,99 @@ describe('Suspense', () => { expect(serializeInner(root)).toBe(`
foo
foo nested
`) }) + // #10098 + test('switching branches w/ nested suspense', async () => { + const RouterView = { + setup(_: any, { slots }: any) { + const route = inject('route') as any + const depth = inject('depth', 0) + provide('depth', depth + 1) + return () => { + const current = route.value[depth] + return slots.default({ Component: current })[0] + } + }, + } + + const OuterB = defineAsyncComponent({ + setup: () => { + return () => + h(RouterView, null, { + default: ({ Component }: any) => [ + h(Suspense, null, { + default: () => h(Component), + }), + ], + }) + }, + }) + + const InnerB = defineAsyncComponent({ + setup: () => { + return () => h('div', 'innerB') + }, + }) + + const OuterA = defineAsyncComponent({ + setup: () => { + return () => + h(RouterView, null, { + default: ({ Component }: any) => [ + h(Suspense, null, { + default: () => h(Component), + }), + ], + }) + }, + }) + + const InnerA = defineAsyncComponent({ + setup: () => { + return () => h('div', 'innerA') + }, + }) + + const toggle = ref(true) + const route = computed(() => { + return toggle.value ? [OuterA, InnerA] : [OuterB, InnerB] + }) + + const Comp = { + setup() { + provide('route', route) + return () => + h(RouterView, null, { + default: ({ Component }: any) => [ + h(Suspense, null, { + default: () => h(Component), + }), + ], + }) + }, + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe(``) + + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe(`
innerA
`) + + deps.length = 0 + + toggle.value = false + await nextTick() + // toggle again + toggle.value = true + + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe(`
innerA
`) + }) + test('branch switch to 3rd branch before resolve', async () => { const calls: string[] = [] diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index 9b3d6765d9c..65b05c3dd7c 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -100,7 +100,9 @@ export const SuspenseImpl = { // it is necessary to skip the current patch to avoid multiple mounts // of inner components. if (parentSuspense && parentSuspense.deps > 0) { - n2.suspense = n1.suspense + n2.suspense = n1.suspense! + n2.suspense.vnode = n2 + n2.el = n1.el return } patchSuspense(