From 5f6661033bd13d462c19998e01f40b30bafded69 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 12 May 2022 11:52:54 +0800 Subject: [PATCH] fix(keep-alive): fix unmounting late-included components fix #3648 based on #3650 --- .../__tests__/components/KeepAlive.spec.ts | 76 ++++++++++++++++++- .../runtime-core/src/components/KeepAlive.ts | 3 +- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts index b60931e1ffb..79e7811723a 100644 --- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts @@ -18,7 +18,12 @@ import { defineAsyncComponent, Component, createApp, - onActivated + onActivated, + onUnmounted, + onMounted, + reactive, + shallowRef, + onDeactivated } from '@vue/runtime-test' import { KeepAliveProps } from '../../src/components/KeepAlive' @@ -903,4 +908,73 @@ describe('KeepAlive', () => { await nextTick() expect(handler).toHaveBeenCalledWith(err, {}, 'activated hook') }) + + // #3648 + test('should avoid unmount later included components', async () => { + const unmountedA = jest.fn() + const mountedA = jest.fn() + const activatedA = jest.fn() + const deactivatedA = jest.fn() + const unmountedB = jest.fn() + const mountedB = jest.fn() + + const A = { + name: 'A', + setup() { + onMounted(mountedA) + onUnmounted(unmountedA) + onActivated(activatedA) + onDeactivated(deactivatedA) + return () => 'A' + } + } + const B = { + name: 'B', + setup() { + onMounted(mountedB) + onUnmounted(unmountedB) + return () => 'B' + } + } + + const include = reactive([]) + const current = shallowRef(A) + const app = createApp({ + setup() { + return () => { + return [ + h( + KeepAlive, + { + include + }, + h(current.value) + ) + ] + } + } + }) + + app.mount(root) + + expect(serializeInner(root)).toBe(`A`) + expect(mountedA).toHaveBeenCalledTimes(1) + expect(unmountedA).toHaveBeenCalledTimes(0) + expect(activatedA).toHaveBeenCalledTimes(0) + expect(deactivatedA).toHaveBeenCalledTimes(0) + expect(mountedB).toHaveBeenCalledTimes(0) + expect(unmountedB).toHaveBeenCalledTimes(0) + + include.push('A') // cache A + await nextTick() + current.value = B // toggle to B + await nextTick() + expect(serializeInner(root)).toBe(`B`) + expect(mountedA).toHaveBeenCalledTimes(1) + expect(unmountedA).toHaveBeenCalledTimes(0) + expect(activatedA).toHaveBeenCalledTimes(0) + expect(deactivatedA).toHaveBeenCalledTimes(1) + expect(mountedB).toHaveBeenCalledTimes(1) + expect(unmountedB).toHaveBeenCalledTimes(0) + }) }) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 15dbff52913..4b2c7288581 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -42,6 +42,7 @@ import { setTransitionHooks } from './BaseTransition' import { ComponentRenderContext } from '../componentPublicInstance' import { devtoolsComponentAdded } from '../devtools' import { isAsyncWrapper } from '../apiAsyncComponent' +import { isSuspense } from './Suspense' type MatchPattern = string | RegExp | (string | RegExp)[] @@ -323,7 +324,7 @@ const KeepAliveImpl: ComponentOptions = { vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE current = vnode - return rawVNode + return isSuspense(rawVNode.type) ? rawVNode : vnode } } }