From 891f3ffc6f220cfcfd5323d0651e26ee0a80fc4b Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Thu, 26 Sep 2024 19:57:10 +0800 Subject: [PATCH 1/9] fix(keep-alive): avoid duplicate mounts of deactivate components --- .../__tests__/components/KeepAlive.spec.ts | 46 +++++++++++++++++++ packages/runtime-core/src/component.ts | 2 + .../runtime-core/src/components/KeepAlive.ts | 2 + packages/runtime-core/src/renderer.ts | 6 ++- 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts index 1e4176b6fb8..7dc1610c4d3 100644 --- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts @@ -7,6 +7,7 @@ import { type TestElement, cloneVNode, createApp, + createVNode, defineAsyncComponent, defineComponent, h, @@ -22,6 +23,7 @@ import { reactive, ref, render, + resolveDynamicComponent, serializeInner, shallowRef, } from '@vue/runtime-test' @@ -1173,4 +1175,48 @@ describe('KeepAlive', () => { expect(deactivatedHome).toHaveBeenCalledTimes(0) expect(unmountedHome).toHaveBeenCalledTimes(1) }) + + // #12017 + test('avoid duplicate mounts of deactivate components', async () => { + const About = { + name: 'About', + setup() { + return () => h('h1', 'About') + }, + } + const mountedHome = vi.fn() + const Home = { + name: 'Home', + setup() { + onMounted(mountedHome) + return () => h('h1', 'Home') + }, + } + const activeView = shallowRef(About) + const HomeView = { + name: 'HomeView', + setup() { + return () => h(createVNode(resolveDynamicComponent(activeView.value))) + }, + } + + const App = createApp({ + setup() { + return () => { + return [ + h(KeepAlive, null, [ + createVNode(resolveDynamicComponent(HomeView), { + key: activeView.value.name, + }), + ]), + ] + } + }, + }) + App.mount(nodeOps.createElement('div')) + expect(mountedHome).toHaveBeenCalledTimes(0) + activeView.value = Home + await nextTick() + expect(mountedHome).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index a1ce1de4eb9..849c79f87a0 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -509,6 +509,7 @@ export interface ComponentInternalInstance { // lifecycle isMounted: boolean isUnmounted: boolean + isDeactive: boolean isDeactivated: boolean /** * @internal @@ -673,6 +674,7 @@ export function createComponentInstance( // not using enums here because it results in computed properties isMounted: false, isUnmounted: false, + isDeactive: false, isDeactivated: false, bc: null, c: null, diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 5976f3a4b33..f0010d3221f 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -136,6 +136,7 @@ const KeepAliveImpl: ComponentOptions = { optimized, ) => { const instance = vnode.component! + instance.isDeactive = false move(vnode, container, anchor, MoveType.ENTER, parentSuspense) // in case props have changed patch( @@ -168,6 +169,7 @@ const KeepAliveImpl: ComponentOptions = { sharedContext.deactivate = (vnode: VNode) => { const instance = vnode.component! + instance.isDeactive = true invalidateMount(instance.m) invalidateMount(instance.a) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 90cc22f5470..c1f3a4759e5 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1429,7 +1429,11 @@ function baseCreateRenderer( // #2458: deference mount-only object parameters to prevent memleaks initialVNode = container = anchor = null as any } else { - let { next, bu, u, parent, vnode } = instance + let { next, bu, u, parent, vnode, isDeactive } = instance + + if (isDeactive) { + return + } if (__FEATURE_SUSPENSE__) { const nonHydratedAsyncRoot = locateNonHydratedAsyncRoot(instance) From 824f3ec982f130303b8bd715f39df09944127667 Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Thu, 26 Sep 2024 23:52:00 +0800 Subject: [PATCH 2/9] fix: update --- packages/runtime-core/src/renderer.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index c1f3a4759e5..110757522c4 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1429,9 +1429,9 @@ function baseCreateRenderer( // #2458: deference mount-only object parameters to prevent memleaks initialVNode = container = anchor = null as any } else { - let { next, bu, u, parent, vnode, isDeactive } = instance + let { next, bu, u, parent, vnode } = instance - if (isDeactive) { + if (checkInstanceActivate(instance)) { return } @@ -2546,6 +2546,17 @@ function locateNonHydratedAsyncRoot( } } +function checkInstanceActivate(instance: ComponentInternalInstance | null) { + while (instance) { + const { isDeactive } = instance + if (isDeactive) { + return true + } + instance = instance.parent + } + return false +} + export function invalidateMount(hooks: LifecycleHook): void { if (hooks) { for (let i = 0; i < hooks.length; i++) From 0e40161988518e430c72b3456707da18abc64355 Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Thu, 26 Sep 2024 23:53:20 +0800 Subject: [PATCH 3/9] chore: update --- packages/runtime-core/src/renderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 110757522c4..02d70706b5d 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1431,7 +1431,7 @@ function baseCreateRenderer( } else { let { next, bu, u, parent, vnode } = instance - if (checkInstanceActivate(instance)) { + if (checkInstanceDeactive(instance)) { return } @@ -2546,7 +2546,7 @@ function locateNonHydratedAsyncRoot( } } -function checkInstanceActivate(instance: ComponentInternalInstance | null) { +function checkInstanceDeactive(instance: ComponentInternalInstance | null) { while (instance) { const { isDeactive } = instance if (isDeactive) { From b15befc92fdd8a4febf0e0436dc393423d01ebba Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Fri, 27 Sep 2024 00:50:04 +0800 Subject: [PATCH 4/9] chore: update --- packages/runtime-core/src/renderer.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 02d70706b5d..6e2d745492b 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2548,11 +2548,16 @@ function locateNonHydratedAsyncRoot( function checkInstanceDeactive(instance: ComponentInternalInstance | null) { while (instance) { - const { isDeactive } = instance - if (isDeactive) { + if (instance.isDeactive) { return true } instance = instance.parent + if ( + instance && + instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE + ) { + break + } } return false } From db4c04141e7c40f493f054cb812078b53ee42d3a Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Fri, 27 Sep 2024 01:48:54 +0800 Subject: [PATCH 5/9] chore: tweek --- packages/runtime-core/src/renderer.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 6e2d745492b..c64076433b2 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2551,13 +2551,10 @@ function checkInstanceDeactive(instance: ComponentInternalInstance | null) { if (instance.isDeactive) { return true } - instance = instance.parent - if ( - instance && - instance.vnode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE - ) { + if (isKeepAlive(instance.vnode)) { break } + instance = instance.parent } return false } From 42ac85d2114bc3926c9a3b3fff60b5e7ee3dccc2 Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Fri, 27 Sep 2024 10:58:32 +0800 Subject: [PATCH 6/9] chore: tweek --- packages/runtime-core/src/component.ts | 4 ++++ packages/runtime-core/src/components/KeepAlive.ts | 6 ++++++ packages/runtime-core/src/renderer.ts | 12 +++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 849c79f87a0..68a1c9642e0 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -506,6 +506,8 @@ export interface ComponentInternalInstance { */ asyncResolved: boolean + keepAliveEffct: Function[] + // lifecycle isMounted: boolean isUnmounted: boolean @@ -670,6 +672,8 @@ export function createComponentInstance( asyncDep: null, asyncResolved: false, + keepAliveEffct: [], + // lifecycle hooks // not using enums here because it results in computed properties isMounted: false, diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index f0010d3221f..0417357677a 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -48,6 +48,7 @@ import { devtoolsComponentAdded } from '../devtools' import { isAsyncWrapper } from '../apiAsyncComponent' import { isSuspense } from './Suspense' import { LifecycleHooks } from '../enums' +import { queuePostFlushCb } from '../scheduler' type MatchPattern = string | RegExp | (string | RegExp)[] @@ -150,6 +151,11 @@ const KeepAliveImpl: ComponentOptions = { vnode.slotScopeIds, optimized, ) + + const effects = instance.keepAliveEffct + queuePostFlushCb(effects) + instance.keepAliveEffct.length = 0 + queuePostRenderEffect(() => { instance.isDeactivated = false if (instance.a) { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index c64076433b2..4229dc266da 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1431,7 +1431,13 @@ function baseCreateRenderer( } else { let { next, bu, u, parent, vnode } = instance - if (checkInstanceDeactive(instance)) { + const keepAliveParent = checkInstanceDeactive(instance) + if (keepAliveParent) { + keepAliveParent.keepAliveEffct.push(() => { + if (!instance.isUnmounted) { + componentUpdateFn() + } + }) return } @@ -2549,14 +2555,14 @@ function locateNonHydratedAsyncRoot( function checkInstanceDeactive(instance: ComponentInternalInstance | null) { while (instance) { if (instance.isDeactive) { - return true + return instance } if (isKeepAlive(instance.vnode)) { break } instance = instance.parent } - return false + return null } export function invalidateMount(hooks: LifecycleHook): void { From c44c2b2a1d426c354b4fae284abc4a1c8590b56f Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Fri, 27 Sep 2024 11:11:30 +0800 Subject: [PATCH 7/9] chore: update --- packages/runtime-core/src/renderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 4229dc266da..6e6983e278c 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1431,7 +1431,7 @@ function baseCreateRenderer( } else { let { next, bu, u, parent, vnode } = instance - const keepAliveParent = checkInstanceDeactive(instance) + const keepAliveParent = locateDeactiveKeeyAlive(instance) if (keepAliveParent) { keepAliveParent.keepAliveEffct.push(() => { if (!instance.isUnmounted) { @@ -2552,7 +2552,7 @@ function locateNonHydratedAsyncRoot( } } -function checkInstanceDeactive(instance: ComponentInternalInstance | null) { +function locateDeactiveKeeyAlive(instance: ComponentInternalInstance | null) { while (instance) { if (instance.isDeactive) { return instance From 0e48e8d5b431db88fe2789417153145b1b1a772c Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Fri, 27 Sep 2024 16:15:23 +0800 Subject: [PATCH 8/9] fix: typo --- packages/runtime-core/src/component.ts | 4 ++-- packages/runtime-core/src/components/KeepAlive.ts | 4 ++-- packages/runtime-core/src/renderer.ts | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 68a1c9642e0..acd410d33ae 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -506,7 +506,7 @@ export interface ComponentInternalInstance { */ asyncResolved: boolean - keepAliveEffct: Function[] + keepAliveEffect: Function[] // lifecycle isMounted: boolean @@ -672,7 +672,7 @@ export function createComponentInstance( asyncDep: null, asyncResolved: false, - keepAliveEffct: [], + keepAliveEffect: [], // lifecycle hooks // not using enums here because it results in computed properties diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 0417357677a..9a7d47086ac 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -152,9 +152,9 @@ const KeepAliveImpl: ComponentOptions = { optimized, ) - const effects = instance.keepAliveEffct + const effects = instance.keepAliveEffect queuePostFlushCb(effects) - instance.keepAliveEffct.length = 0 + instance.keepAliveEffect.length = 0 queuePostRenderEffect(() => { instance.isDeactivated = false diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 6e6983e278c..c3be63972ee 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1431,9 +1431,9 @@ function baseCreateRenderer( } else { let { next, bu, u, parent, vnode } = instance - const keepAliveParent = locateDeactiveKeeyAlive(instance) + const keepAliveParent = locateDeactiveKeepAlive(instance) if (keepAliveParent) { - keepAliveParent.keepAliveEffct.push(() => { + keepAliveParent.keepAliveEffect.push(() => { if (!instance.isUnmounted) { componentUpdateFn() } @@ -2552,7 +2552,7 @@ function locateNonHydratedAsyncRoot( } } -function locateDeactiveKeeyAlive(instance: ComponentInternalInstance | null) { +function locateDeactiveKeepAlive(instance: ComponentInternalInstance | null) { while (instance) { if (instance.isDeactive) { return instance From e767b9381bc50c48ffb2adf08a997ab7c702b3d0 Mon Sep 17 00:00:00 2001 From: yangchangtao Date: Sun, 29 Sep 2024 14:38:33 +0800 Subject: [PATCH 9/9] chore: update --- .../runtime-core/__tests__/components/KeepAlive.spec.ts | 6 ++---- packages/runtime-core/src/component.ts | 4 ++-- packages/runtime-core/src/components/KeepAlive.ts | 4 ++-- packages/runtime-core/src/renderer.ts | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts index 7dc1610c4d3..7214e6c2e80 100644 --- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts @@ -7,7 +7,6 @@ import { type TestElement, cloneVNode, createApp, - createVNode, defineAsyncComponent, defineComponent, h, @@ -23,7 +22,6 @@ import { reactive, ref, render, - resolveDynamicComponent, serializeInner, shallowRef, } from '@vue/runtime-test' @@ -1196,7 +1194,7 @@ describe('KeepAlive', () => { const HomeView = { name: 'HomeView', setup() { - return () => h(createVNode(resolveDynamicComponent(activeView.value))) + return () => h(activeView.value) }, } @@ -1205,7 +1203,7 @@ describe('KeepAlive', () => { return () => { return [ h(KeepAlive, null, [ - createVNode(resolveDynamicComponent(HomeView), { + h(HomeView, { key: activeView.value.name, }), ]), diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index acd410d33ae..4137dfd41a2 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -511,7 +511,7 @@ export interface ComponentInternalInstance { // lifecycle isMounted: boolean isUnmounted: boolean - isDeactive: boolean + isActivated: boolean isDeactivated: boolean /** * @internal @@ -678,7 +678,7 @@ export function createComponentInstance( // not using enums here because it results in computed properties isMounted: false, isUnmounted: false, - isDeactive: false, + isActivated: false, isDeactivated: false, bc: null, c: null, diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 9a7d47086ac..bc77cf9ba3f 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -137,7 +137,7 @@ const KeepAliveImpl: ComponentOptions = { optimized, ) => { const instance = vnode.component! - instance.isDeactive = false + instance.isActivated = false move(vnode, container, anchor, MoveType.ENTER, parentSuspense) // in case props have changed patch( @@ -175,7 +175,7 @@ const KeepAliveImpl: ComponentOptions = { sharedContext.deactivate = (vnode: VNode) => { const instance = vnode.component! - instance.isDeactive = true + instance.isActivated = true invalidateMount(instance.m) invalidateMount(instance.a) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index c3be63972ee..db0efe83890 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -2554,7 +2554,7 @@ function locateNonHydratedAsyncRoot( function locateDeactiveKeepAlive(instance: ComponentInternalInstance | null) { while (instance) { - if (instance.isDeactive) { + if (instance.isActivated) { return instance } if (isKeepAlive(instance.vnode)) {