Skip to content

Commit d6a6ec1

Browse files
authored
fix(runtime-core): prevent unmounted vnode from being inserted during transition leave (#12862)
close #12860
1 parent 263f63f commit d6a6ec1

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed

packages/runtime-core/src/components/KeepAlive.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ const KeepAliveImpl: ComponentOptions = {
187187
// Update components tree
188188
devtoolsComponentAdded(instance)
189189
}
190+
191+
// for e2e test
192+
if (__DEV__ && __BROWSER__) {
193+
;(instance as any).__keepAliveStorageContainer = storageContainer
194+
}
190195
}
191196

192197
function unmount(vnode: VNode) {

packages/runtime-core/src/renderer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2049,7 +2049,13 @@ function baseCreateRenderer(
20492049
queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
20502050
} else {
20512051
const { leave, delayLeave, afterLeave } = transition!
2052-
const remove = () => hostInsert(el!, container, anchor)
2052+
const remove = () => {
2053+
if (vnode.ctx!.isUnmounted) {
2054+
hostRemove(el!)
2055+
} else {
2056+
hostInsert(el!, container, anchor)
2057+
}
2058+
}
20532059
const performLeave = () => {
20542060
leave(el!, () => {
20552061
remove()

packages/vue/__tests__/e2e/Transition.spec.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ElementHandle } from 'puppeteer'
12
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
23
import path from 'node:path'
34
import { Transition, createApp, h, nextTick, ref } from 'vue'
@@ -1653,6 +1654,74 @@ describe('e2e: Transition', () => {
16531654
},
16541655
E2E_TIMEOUT,
16551656
)
1657+
1658+
// #12860
1659+
test(
1660+
'unmount children',
1661+
async () => {
1662+
const unmountSpy = vi.fn()
1663+
let storageContainer: ElementHandle<HTMLDivElement>
1664+
const setStorageContainer = (container: any) =>
1665+
(storageContainer = container)
1666+
await page().exposeFunction('unmountSpy', unmountSpy)
1667+
await page().exposeFunction('setStorageContainer', setStorageContainer)
1668+
await page().evaluate(() => {
1669+
const { unmountSpy, setStorageContainer } = window as any
1670+
const { createApp, ref, h, onUnmounted, getCurrentInstance } = (
1671+
window as any
1672+
).Vue
1673+
createApp({
1674+
template: `
1675+
<div id="container">
1676+
<transition>
1677+
<KeepAlive :include="includeRef">
1678+
<TrueBranch v-if="toggle"></TrueBranch>
1679+
</KeepAlive>
1680+
</transition>
1681+
</div>
1682+
<button id="toggleBtn" @click="click">button</button>
1683+
`,
1684+
components: {
1685+
TrueBranch: {
1686+
name: 'TrueBranch',
1687+
setup() {
1688+
const instance = getCurrentInstance()
1689+
onUnmounted(() => {
1690+
unmountSpy()
1691+
setStorageContainer(instance.__keepAliveStorageContainer)
1692+
})
1693+
const count = ref(0)
1694+
return () => h('div', count.value)
1695+
},
1696+
},
1697+
},
1698+
setup: () => {
1699+
const includeRef = ref(['TrueBranch'])
1700+
const toggle = ref(true)
1701+
const click = () => {
1702+
toggle.value = !toggle.value
1703+
if (toggle.value) {
1704+
includeRef.value = ['TrueBranch']
1705+
} else {
1706+
includeRef.value = []
1707+
}
1708+
}
1709+
return { toggle, click, unmountSpy, includeRef }
1710+
},
1711+
}).mount('#app')
1712+
})
1713+
1714+
await transitionFinish()
1715+
expect(await html('#container')).toBe('<div>0</div>')
1716+
1717+
await click('#toggleBtn')
1718+
await transitionFinish()
1719+
expect(await html('#container')).toBe('<!--v-if-->')
1720+
expect(unmountSpy).toBeCalledTimes(1)
1721+
expect(await storageContainer!.evaluate(x => x.innerHTML)).toBe(``)
1722+
},
1723+
E2E_TIMEOUT,
1724+
)
16561725
})
16571726

16581727
describe('transition with Suspense', () => {

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy