mirror of https://github.com/vuejs/core.git
fix(suspense): avoid double-patching nested suspense when parent suspense is not resolved (#10055)
close #8678
This commit is contained in:
parent
07b19a53a5
commit
bcda96b525
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue