mirror of https://github.com/vuejs/core.git
Merge a94eb54365
into ba391f5fdf
This commit is contained in:
commit
6b5c0aae3f
|
@ -418,6 +418,7 @@ export interface SuspenseBoundary {
|
|||
container: RendererElement
|
||||
hiddenContainer: RendererElement
|
||||
activeBranch: VNode | null
|
||||
initialContent: VNode | null
|
||||
pendingBranch: VNode | null
|
||||
deps: number
|
||||
pendingId: number
|
||||
|
@ -503,6 +504,7 @@ function createSuspenseBoundary(
|
|||
pendingId: suspenseId++,
|
||||
timeout: typeof timeout === 'number' ? timeout : -1,
|
||||
activeBranch: null,
|
||||
initialContent: null,
|
||||
pendingBranch: null,
|
||||
isInFallback: !isHydrating,
|
||||
isHydrating,
|
||||
|
@ -555,7 +557,11 @@ function createSuspenseBoundary(
|
|||
}
|
||||
}
|
||||
// unmount current active tree
|
||||
if (activeBranch) {
|
||||
// #7966 when Suspense is wrapped in Transition, the fallback node will be mounted
|
||||
// in the afterLeave of Transition. This means that when Suspense is resolved,
|
||||
// the activeBranch is not the fallback node but the initialContent.
|
||||
// so avoid unmounting the activeBranch again.
|
||||
if (activeBranch && activeBranch !== suspense.initialContent) {
|
||||
// if the fallback tree was mounted, it may have been moved
|
||||
// as part of a parent suspense. get the latest anchor for insertion
|
||||
// #8105 if `delayEnter` is true, it means that the mounting of
|
||||
|
@ -632,6 +638,7 @@ function createSuspenseBoundary(
|
|||
|
||||
const anchor = next(activeBranch!)
|
||||
const mountFallback = () => {
|
||||
suspense.initialContent = null
|
||||
if (!suspense.isInFallback) {
|
||||
return
|
||||
}
|
||||
|
@ -653,6 +660,7 @@ function createSuspenseBoundary(
|
|||
const delayEnter =
|
||||
fallbackVNode.transition && fallbackVNode.transition.mode === 'out-in'
|
||||
if (delayEnter) {
|
||||
suspense.initialContent = activeBranch!
|
||||
activeBranch!.transition!.afterLeave = mountFallback
|
||||
}
|
||||
suspense.isInFallback = true
|
||||
|
|
|
@ -2007,6 +2007,74 @@ describe('e2e: Transition', () => {
|
|||
E2E_TIMEOUT,
|
||||
)
|
||||
|
||||
test(
|
||||
'avoid unmount activeBranch twice with Suspense (out-in mode + timeout="0")',
|
||||
async () => {
|
||||
const unmountSpy = vi.fn()
|
||||
await page().exposeFunction('unmountSpy', unmountSpy)
|
||||
await page().evaluate(() => {
|
||||
const { createApp, shallowRef, h } = (window as any).Vue
|
||||
const One = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
'div',
|
||||
{
|
||||
onVnodeBeforeUnmount: () => unmountSpy(),
|
||||
},
|
||||
'one',
|
||||
)
|
||||
},
|
||||
}
|
||||
const Two = {
|
||||
async setup() {
|
||||
return () => h('div', null, 'two')
|
||||
},
|
||||
}
|
||||
createApp({
|
||||
template: `
|
||||
<div id="container">
|
||||
<transition mode="out-in">
|
||||
<suspense timeout="0">
|
||||
<template #default>
|
||||
<component :is="view"></component>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div>Loading...</div>
|
||||
</template>
|
||||
</suspense>
|
||||
</transition>
|
||||
</div>
|
||||
<button id="toggleBtn" @click="click">button</button>
|
||||
`,
|
||||
setup: () => {
|
||||
const view = shallowRef(One)
|
||||
const click = () => {
|
||||
view.value = view.value === One ? Two : One
|
||||
}
|
||||
return { view, click }
|
||||
},
|
||||
}).mount('#app')
|
||||
})
|
||||
|
||||
expect(await html('#container')).toBe('<div>one</div>')
|
||||
|
||||
// leave
|
||||
await classWhenTransitionStart()
|
||||
await nextFrame()
|
||||
expect(await html('#container')).toBe(
|
||||
'<div class="v-enter-from v-enter-active">two</div>',
|
||||
)
|
||||
|
||||
await transitionFinish()
|
||||
expect(await html('#container')).toBe('<div class="">two</div>')
|
||||
|
||||
// should only call unmount once
|
||||
expect(unmountSpy).toBeCalledTimes(1)
|
||||
},
|
||||
E2E_TIMEOUT,
|
||||
)
|
||||
|
||||
// #5844
|
||||
test('children mount should be called after html changes', async () => {
|
||||
const fooMountSpy = vi.fn()
|
||||
|
|
Loading…
Reference in New Issue