Skip to content

Commit

Permalink
fix(suspense): avoid double-patching nested suspense when parent susp…
Browse files Browse the repository at this point in the history
…ense is not resolved (vuejs#10055)

close vuejs#8678
  • Loading branch information
edison1105 committed Jan 11, 2024
1 parent 848ba55 commit 39cca06
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 0 deletions.
135 changes: 135 additions & 0 deletions packages/runtime-core/__tests__/components/Suspense.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1641,6 +1641,141 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(expected)
})

//#8678
test('nested suspense (child suspense update before parent suspense resolve)', async () => {
const calls: string[] = []

const InnerA = defineAsyncComponent(
{
setup: () => {
calls.push('innerA created')
onMounted(() => {
calls.push('innerA mounted')
})
return () => h('div', 'innerA')
},
},
10,
)

const InnerB = defineAsyncComponent(
{
setup: () => {
calls.push('innerB created')
onMounted(() => {
calls.push('innerB mounted')
})
return () => h('div', 'innerB')
},
},
10,
)

const OuterA = defineAsyncComponent(
{
setup: (_, { slots }: any) => {
calls.push('outerA created')
onMounted(() => {
calls.push('outerA mounted')
})
return () =>
h(Fragment, null, [h('div', 'outerA'), slots.default?.()])
},
},
5,
)

const OuterB = defineAsyncComponent(
{
setup: (_, { slots }: any) => {
calls.push('outerB created')
onMounted(() => {
calls.push('outerB mounted')
})
return () =>
h(Fragment, null, [h('div', 'outerB'), slots.default?.()])
},
},
5,
)

const outerToggle = ref(false)
const innerToggle = ref(false)

/**
* <Suspense>
* <component :is="outerToggle ? outerB : outerA">
* <Suspense>
* <component :is="innerToggle ? innerB : innerA" />
* </Suspense>
* </component>
* </Suspense>
*/
const Comp = {
setup() {
return () =>
h(Suspense, null, {
default: [
h(outerToggle.value ? OuterB : OuterA, null, {
default: () =>
h(Suspense, null, {
default: h(innerToggle.value ? InnerB : InnerA),
}),
}),
],
fallback: h('div', 'fallback outer'),
})
},
}

const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>fallback outer</div>`)

// mount outer component
await Promise.all(deps)
await nextTick()

expect(serializeInner(root)).toBe(`<div>outerA</div><!---->`)
expect(calls).toEqual([`outerA created`, `outerA mounted`])

// mount inner component
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>outerA</div><div>innerA</div>`)

expect(calls).toEqual([
'outerA created',
'outerA mounted',
'innerA created',
'innerA mounted',
])

calls.length = 0
deps.length = 0

// toggle both outer and inner components
outerToggle.value = true
innerToggle.value = true
await nextTick()

await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>outerB</div><!---->`)

await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>outerB</div><div>innerB</div>`)

// innerB only mount once
expect(calls).toEqual([
'outerB created',
'outerB mounted',
'innerB created',
'innerB mounted',
])
})

// #6416
test('KeepAlive with Suspense', async () => {
const Async = defineAsyncComponent({
Expand Down
12 changes: 12 additions & 0 deletions packages/runtime-core/src/components/Suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ export const SuspenseImpl = {
rendererInternals,
)
} else {
// #8678 if the current suspense needs to be patched and parentSuspense has
// not been resolved. this means that both the current suspense and parentSuspense
// need to be patched. because parentSuspense's pendingBranch includes the
// current suspense, it will be processed twice:
// 1. current patch
// 2. mounting along with the pendingBranch of parentSuspense
// it is necessary to skip the current patch to avoid multiple mounts
// of inner components.
if (parentSuspense && parentSuspense.deps > 0) {
n2.suspense = n1.suspense
return
}
patchSuspense(
n1,
n2,
Expand Down

0 comments on commit 39cca06

Please sign in to comment.