Skip to content

Commit 0f3da05

Browse files
authoredFeb 25, 2024··
fix(suspense): handle suspense switching with nested suspense (#10184)
close #10098
1 parent 411596c commit 0f3da05

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed
 

‎packages/runtime-core/__tests__/components/Suspense.spec.ts

+94-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
watch,
2323
watchEffect,
2424
} from '@vue/runtime-test'
25-
import { createApp, defineComponent } from 'vue'
25+
import { computed, createApp, defineComponent, inject, provide } from 'vue'
2626
import type { RawSlots } from 'packages/runtime-core/src/componentSlots'
2727
import { resetSuspenseId } from '../../src/components/Suspense'
2828

@@ -1039,6 +1039,99 @@ describe('Suspense', () => {
10391039
expect(serializeInner(root)).toBe(`<div>foo<div>foo nested</div></div>`)
10401040
})
10411041

1042+
// #10098
1043+
test('switching branches w/ nested suspense', async () => {
1044+
const RouterView = {
1045+
setup(_: any, { slots }: any) {
1046+
const route = inject('route') as any
1047+
const depth = inject('depth', 0)
1048+
provide('depth', depth + 1)
1049+
return () => {
1050+
const current = route.value[depth]
1051+
return slots.default({ Component: current })[0]
1052+
}
1053+
},
1054+
}
1055+
1056+
const OuterB = defineAsyncComponent({
1057+
setup: () => {
1058+
return () =>
1059+
h(RouterView, null, {
1060+
default: ({ Component }: any) => [
1061+
h(Suspense, null, {
1062+
default: () => h(Component),
1063+
}),
1064+
],
1065+
})
1066+
},
1067+
})
1068+
1069+
const InnerB = defineAsyncComponent({
1070+
setup: () => {
1071+
return () => h('div', 'innerB')
1072+
},
1073+
})
1074+
1075+
const OuterA = defineAsyncComponent({
1076+
setup: () => {
1077+
return () =>
1078+
h(RouterView, null, {
1079+
default: ({ Component }: any) => [
1080+
h(Suspense, null, {
1081+
default: () => h(Component),
1082+
}),
1083+
],
1084+
})
1085+
},
1086+
})
1087+
1088+
const InnerA = defineAsyncComponent({
1089+
setup: () => {
1090+
return () => h('div', 'innerA')
1091+
},
1092+
})
1093+
1094+
const toggle = ref(true)
1095+
const route = computed(() => {
1096+
return toggle.value ? [OuterA, InnerA] : [OuterB, InnerB]
1097+
})
1098+
1099+
const Comp = {
1100+
setup() {
1101+
provide('route', route)
1102+
return () =>
1103+
h(RouterView, null, {
1104+
default: ({ Component }: any) => [
1105+
h(Suspense, null, {
1106+
default: () => h(Component),
1107+
}),
1108+
],
1109+
})
1110+
},
1111+
}
1112+
1113+
const root = nodeOps.createElement('div')
1114+
render(h(Comp), root)
1115+
await Promise.all(deps)
1116+
await nextTick()
1117+
expect(serializeInner(root)).toBe(`<!---->`)
1118+
1119+
await Promise.all(deps)
1120+
await nextTick()
1121+
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
1122+
1123+
deps.length = 0
1124+
1125+
toggle.value = false
1126+
await nextTick()
1127+
// toggle again
1128+
toggle.value = true
1129+
1130+
await Promise.all(deps)
1131+
await nextTick()
1132+
expect(serializeInner(root)).toBe(`<div>innerA</div>`)
1133+
})
1134+
10421135
test('branch switch to 3rd branch before resolve', async () => {
10431136
const calls: string[] = []
10441137

‎packages/runtime-core/src/components/Suspense.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ export const SuspenseImpl = {
100100
// it is necessary to skip the current patch to avoid multiple mounts
101101
// of inner components.
102102
if (parentSuspense && parentSuspense.deps > 0) {
103-
n2.suspense = n1.suspense
103+
n2.suspense = n1.suspense!
104+
n2.suspense.vnode = n2
105+
n2.el = n1.el
104106
return
105107
}
106108
patchSuspense(

0 commit comments

Comments
 (0)
Please sign in to comment.