From aa156ed5c4dc0d33ff37e201a7e89d5e0e29160e Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 7 Jun 2024 13:48:50 +0800 Subject: [PATCH] fix(runtime-core): do not fire mount/activated hooks if unmounted before mounted (#9370) close #8898 close #9264 close #9617 --- .../__tests__/apiLifecycle.spec.ts | 58 +++++++++++++++++++ packages/runtime-core/src/apiLifecycle.ts | 3 - packages/runtime-core/src/component.ts | 2 +- .../runtime-core/src/components/KeepAlive.ts | 4 ++ packages/runtime-core/src/renderer.ts | 11 +++- packages/runtime-core/src/scheduler.ts | 8 +-- 6 files changed, 76 insertions(+), 10 deletions(-) diff --git a/packages/runtime-core/__tests__/apiLifecycle.spec.ts b/packages/runtime-core/__tests__/apiLifecycle.spec.ts index 43054800afe..5a0da4d6789 100644 --- a/packages/runtime-core/__tests__/apiLifecycle.spec.ts +++ b/packages/runtime-core/__tests__/apiLifecycle.spec.ts @@ -1,8 +1,10 @@ import { + KeepAlive, TrackOpTypes, h, nextTick, nodeOps, + onActivated, onBeforeMount, onBeforeUnmount, onBeforeUpdate, @@ -407,4 +409,60 @@ describe('api: lifecycle hooks', () => { await nextTick() expect(fn).toHaveBeenCalledTimes(4) }) + + it('immediately trigger unmount during rendering', async () => { + const fn = vi.fn() + const toggle = ref(false) + + const Child = { + setup() { + onMounted(fn) + // trigger unmount immediately + toggle.value = false + return () => h('div') + }, + } + + const Comp = { + setup() { + return () => (toggle.value ? [h(Child)] : null) + }, + } + + render(h(Comp), nodeOps.createElement('div')) + + toggle.value = true + await nextTick() + expect(fn).toHaveBeenCalledTimes(0) + }) + + it('immediately trigger unmount during rendering(with KeepAlive)', async () => { + const mountedSpy = vi.fn() + const activeSpy = vi.fn() + const toggle = ref(false) + + const Child = { + setup() { + onMounted(mountedSpy) + onActivated(activeSpy) + + // trigger unmount immediately + toggle.value = false + return () => h('div') + }, + } + + const Comp = { + setup() { + return () => h(KeepAlive, [toggle.value ? h(Child) : null]) + }, + } + + render(h(Comp), nodeOps.createElement('div')) + + toggle.value = true + await nextTick() + expect(mountedSpy).toHaveBeenCalledTimes(0) + expect(activeSpy).toHaveBeenCalledTimes(0) + }) }) diff --git a/packages/runtime-core/src/apiLifecycle.ts b/packages/runtime-core/src/apiLifecycle.ts index cfaf7636e9a..72873bc8a1b 100644 --- a/packages/runtime-core/src/apiLifecycle.ts +++ b/packages/runtime-core/src/apiLifecycle.ts @@ -31,9 +31,6 @@ export function injectHook( const wrappedHook = hook.__weh || (hook.__weh = (...args: unknown[]) => { - if (target.isUnmounted) { - return - } // disable tracking inside all lifecycle hooks // since they can potentially be called inside effects. pauseTracking() diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index a658f83db5b..f2ca0464142 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -223,7 +223,7 @@ export type Component< export type { ComponentOptions } -type LifecycleHook = TFn[] | null +export type LifecycleHook = (TFn & SchedulerJob)[] | null // use `E extends any` to force evaluating type to fix #2362 export type SetupContext< diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 326c2f95dbb..a2c8c2bf1a7 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -38,6 +38,7 @@ import { type RendererElement, type RendererInternals, type RendererNode, + invalidateMount, queuePostRenderEffect, } from '../renderer' import { setTransitionHooks } from './BaseTransition' @@ -166,6 +167,9 @@ const KeepAliveImpl: ComponentOptions = { sharedContext.deactivate = (vnode: VNode) => { const instance = vnode.component! + invalidateMount(instance.m) + invalidateMount(instance.a) + move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) queuePostRenderEffect(() => { if (instance.da) { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index cbb08da7bc7..d06c6858415 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -17,6 +17,7 @@ import { type ComponentInternalInstance, type ComponentOptions, type Data, + type LifecycleHook, createComponentInstance, setupComponent, } from './component' @@ -2266,7 +2267,9 @@ function baseCreateRenderer( unregisterHMR(instance) } - const { bum, scope, update, subTree, um } = instance + const { bum, scope, update, subTree, um, m, a } = instance + invalidateMount(m) + invalidateMount(a) // beforeUnmount hook if (bum) { @@ -2533,3 +2536,9 @@ function locateNonHydratedAsyncRoot( } } } + +export function invalidateMount(hooks: LifecycleHook) { + if (hooks) { + for (let i = 0; i < hooks.length; i++) hooks[i].active = false + } +} diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index b8d1445a183..4ae1c6d46e7 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -185,13 +185,11 @@ export function flushPostFlushCbs(seen?: CountMap) { postFlushIndex < activePostFlushCbs.length; postFlushIndex++ ) { - if ( - __DEV__ && - checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex]) - ) { + const cb = activePostFlushCbs[postFlushIndex] + if (__DEV__ && checkRecursiveUpdates(seen!, cb)) { continue } - activePostFlushCbs[postFlushIndex]() + if (cb.active !== false) cb() } activePostFlushCbs = null postFlushIndex = 0