fix(runtime-core): do not fire mount/activated hooks if unmounted before mounted (#9370)

close #8898
close #9264
close #9617
This commit is contained in:
edison 2024-06-07 13:48:50 +08:00 committed by GitHub
parent 32262a9af5
commit aa156ed5c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 10 deletions

View File

@ -1,8 +1,10 @@
import { import {
KeepAlive,
TrackOpTypes, TrackOpTypes,
h, h,
nextTick, nextTick,
nodeOps, nodeOps,
onActivated,
onBeforeMount, onBeforeMount,
onBeforeUnmount, onBeforeUnmount,
onBeforeUpdate, onBeforeUpdate,
@ -407,4 +409,60 @@ describe('api: lifecycle hooks', () => {
await nextTick() await nextTick()
expect(fn).toHaveBeenCalledTimes(4) 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)
})
}) })

View File

@ -31,9 +31,6 @@ export function injectHook(
const wrappedHook = const wrappedHook =
hook.__weh || hook.__weh ||
(hook.__weh = (...args: unknown[]) => { (hook.__weh = (...args: unknown[]) => {
if (target.isUnmounted) {
return
}
// disable tracking inside all lifecycle hooks // disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects. // since they can potentially be called inside effects.
pauseTracking() pauseTracking()

View File

@ -223,7 +223,7 @@ export type Component<
export type { ComponentOptions } export type { ComponentOptions }
type LifecycleHook<TFn = Function> = TFn[] | null export type LifecycleHook<TFn = Function> = (TFn & SchedulerJob)[] | null
// use `E extends any` to force evaluating type to fix #2362 // use `E extends any` to force evaluating type to fix #2362
export type SetupContext< export type SetupContext<

View File

@ -38,6 +38,7 @@ import {
type RendererElement, type RendererElement,
type RendererInternals, type RendererInternals,
type RendererNode, type RendererNode,
invalidateMount,
queuePostRenderEffect, queuePostRenderEffect,
} from '../renderer' } from '../renderer'
import { setTransitionHooks } from './BaseTransition' import { setTransitionHooks } from './BaseTransition'
@ -166,6 +167,9 @@ const KeepAliveImpl: ComponentOptions = {
sharedContext.deactivate = (vnode: VNode) => { sharedContext.deactivate = (vnode: VNode) => {
const instance = vnode.component! const instance = vnode.component!
invalidateMount(instance.m)
invalidateMount(instance.a)
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
queuePostRenderEffect(() => { queuePostRenderEffect(() => {
if (instance.da) { if (instance.da) {

View File

@ -17,6 +17,7 @@ import {
type ComponentInternalInstance, type ComponentInternalInstance,
type ComponentOptions, type ComponentOptions,
type Data, type Data,
type LifecycleHook,
createComponentInstance, createComponentInstance,
setupComponent, setupComponent,
} from './component' } from './component'
@ -2266,7 +2267,9 @@ function baseCreateRenderer(
unregisterHMR(instance) unregisterHMR(instance)
} }
const { bum, scope, update, subTree, um } = instance const { bum, scope, update, subTree, um, m, a } = instance
invalidateMount(m)
invalidateMount(a)
// beforeUnmount hook // beforeUnmount hook
if (bum) { 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
}
}

View File

@ -185,13 +185,11 @@ export function flushPostFlushCbs(seen?: CountMap) {
postFlushIndex < activePostFlushCbs.length; postFlushIndex < activePostFlushCbs.length;
postFlushIndex++ postFlushIndex++
) { ) {
if ( const cb = activePostFlushCbs[postFlushIndex]
__DEV__ && if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex])
) {
continue continue
} }
activePostFlushCbs[postFlushIndex]() if (cb.active !== false) cb()
} }
activePostFlushCbs = null activePostFlushCbs = null
postFlushIndex = 0 postFlushIndex = 0