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
|
container: RendererElement
|
||||||
hiddenContainer: RendererElement
|
hiddenContainer: RendererElement
|
||||||
activeBranch: VNode | null
|
activeBranch: VNode | null
|
||||||
|
initialContent: VNode | null
|
||||||
pendingBranch: VNode | null
|
pendingBranch: VNode | null
|
||||||
deps: number
|
deps: number
|
||||||
pendingId: number
|
pendingId: number
|
||||||
|
@ -503,6 +504,7 @@ function createSuspenseBoundary(
|
||||||
pendingId: suspenseId++,
|
pendingId: suspenseId++,
|
||||||
timeout: typeof timeout === 'number' ? timeout : -1,
|
timeout: typeof timeout === 'number' ? timeout : -1,
|
||||||
activeBranch: null,
|
activeBranch: null,
|
||||||
|
initialContent: null,
|
||||||
pendingBranch: null,
|
pendingBranch: null,
|
||||||
isInFallback: !isHydrating,
|
isInFallback: !isHydrating,
|
||||||
isHydrating,
|
isHydrating,
|
||||||
|
@ -555,7 +557,11 @@ function createSuspenseBoundary(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// unmount current active tree
|
// 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
|
// if the fallback tree was mounted, it may have been moved
|
||||||
// as part of a parent suspense. get the latest anchor for insertion
|
// as part of a parent suspense. get the latest anchor for insertion
|
||||||
// #8105 if `delayEnter` is true, it means that the mounting of
|
// #8105 if `delayEnter` is true, it means that the mounting of
|
||||||
|
@ -632,6 +638,7 @@ function createSuspenseBoundary(
|
||||||
|
|
||||||
const anchor = next(activeBranch!)
|
const anchor = next(activeBranch!)
|
||||||
const mountFallback = () => {
|
const mountFallback = () => {
|
||||||
|
suspense.initialContent = null
|
||||||
if (!suspense.isInFallback) {
|
if (!suspense.isInFallback) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -653,6 +660,7 @@ function createSuspenseBoundary(
|
||||||
const delayEnter =
|
const delayEnter =
|
||||||
fallbackVNode.transition && fallbackVNode.transition.mode === 'out-in'
|
fallbackVNode.transition && fallbackVNode.transition.mode === 'out-in'
|
||||||
if (delayEnter) {
|
if (delayEnter) {
|
||||||
|
suspense.initialContent = activeBranch!
|
||||||
activeBranch!.transition!.afterLeave = mountFallback
|
activeBranch!.transition!.afterLeave = mountFallback
|
||||||
}
|
}
|
||||||
suspense.isInFallback = true
|
suspense.isInFallback = true
|
||||||
|
|
|
@ -2007,6 +2007,74 @@ describe('e2e: Transition', () => {
|
||||||
E2E_TIMEOUT,
|
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
|
// #5844
|
||||||
test('children mount should be called after html changes', async () => {
|
test('children mount should be called after html changes', async () => {
|
||||||
const fooMountSpy = vi.fn()
|
const fooMountSpy = vi.fn()
|
||||||
|
|
Loading…
Reference in New Issue