Skip to content

Commit aa156ed

Browse files
authored
fix(runtime-core): do not fire mount/activated hooks if unmounted before mounted (#9370)
close #8898 close #9264 close #9617
1 parent 32262a9 commit aa156ed

File tree

6 files changed

+76
-10
lines changed

6 files changed

+76
-10
lines changed

packages/runtime-core/__tests__/apiLifecycle.spec.ts

+58
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {
2+
KeepAlive,
23
TrackOpTypes,
34
h,
45
nextTick,
56
nodeOps,
7+
onActivated,
68
onBeforeMount,
79
onBeforeUnmount,
810
onBeforeUpdate,
@@ -407,4 +409,60 @@ describe('api: lifecycle hooks', () => {
407409
await nextTick()
408410
expect(fn).toHaveBeenCalledTimes(4)
409411
})
412+
413+
it('immediately trigger unmount during rendering', async () => {
414+
const fn = vi.fn()
415+
const toggle = ref(false)
416+
417+
const Child = {
418+
setup() {
419+
onMounted(fn)
420+
// trigger unmount immediately
421+
toggle.value = false
422+
return () => h('div')
423+
},
424+
}
425+
426+
const Comp = {
427+
setup() {
428+
return () => (toggle.value ? [h(Child)] : null)
429+
},
430+
}
431+
432+
render(h(Comp), nodeOps.createElement('div'))
433+
434+
toggle.value = true
435+
await nextTick()
436+
expect(fn).toHaveBeenCalledTimes(0)
437+
})
438+
439+
it('immediately trigger unmount during rendering(with KeepAlive)', async () => {
440+
const mountedSpy = vi.fn()
441+
const activeSpy = vi.fn()
442+
const toggle = ref(false)
443+
444+
const Child = {
445+
setup() {
446+
onMounted(mountedSpy)
447+
onActivated(activeSpy)
448+
449+
// trigger unmount immediately
450+
toggle.value = false
451+
return () => h('div')
452+
},
453+
}
454+
455+
const Comp = {
456+
setup() {
457+
return () => h(KeepAlive, [toggle.value ? h(Child) : null])
458+
},
459+
}
460+
461+
render(h(Comp), nodeOps.createElement('div'))
462+
463+
toggle.value = true
464+
await nextTick()
465+
expect(mountedSpy).toHaveBeenCalledTimes(0)
466+
expect(activeSpy).toHaveBeenCalledTimes(0)
467+
})
410468
})

packages/runtime-core/src/apiLifecycle.ts

-3
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ export function injectHook(
3131
const wrappedHook =
3232
hook.__weh ||
3333
(hook.__weh = (...args: unknown[]) => {
34-
if (target.isUnmounted) {
35-
return
36-
}
3734
// disable tracking inside all lifecycle hooks
3835
// since they can potentially be called inside effects.
3936
pauseTracking()

packages/runtime-core/src/component.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ export type Component<
223223

224224
export type { ComponentOptions }
225225

226-
type LifecycleHook<TFn = Function> = TFn[] | null
226+
export type LifecycleHook<TFn = Function> = (TFn & SchedulerJob)[] | null
227227

228228
// use `E extends any` to force evaluating type to fix #2362
229229
export type SetupContext<

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

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
type RendererElement,
3939
type RendererInternals,
4040
type RendererNode,
41+
invalidateMount,
4142
queuePostRenderEffect,
4243
} from '../renderer'
4344
import { setTransitionHooks } from './BaseTransition'
@@ -166,6 +167,9 @@ const KeepAliveImpl: ComponentOptions = {
166167

167168
sharedContext.deactivate = (vnode: VNode) => {
168169
const instance = vnode.component!
170+
invalidateMount(instance.m)
171+
invalidateMount(instance.a)
172+
169173
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
170174
queuePostRenderEffect(() => {
171175
if (instance.da) {

packages/runtime-core/src/renderer.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
type ComponentInternalInstance,
1818
type ComponentOptions,
1919
type Data,
20+
type LifecycleHook,
2021
createComponentInstance,
2122
setupComponent,
2223
} from './component'
@@ -2266,7 +2267,9 @@ function baseCreateRenderer(
22662267
unregisterHMR(instance)
22672268
}
22682269

2269-
const { bum, scope, update, subTree, um } = instance
2270+
const { bum, scope, update, subTree, um, m, a } = instance
2271+
invalidateMount(m)
2272+
invalidateMount(a)
22702273

22712274
// beforeUnmount hook
22722275
if (bum) {
@@ -2533,3 +2536,9 @@ function locateNonHydratedAsyncRoot(
25332536
}
25342537
}
25352538
}
2539+
2540+
export function invalidateMount(hooks: LifecycleHook) {
2541+
if (hooks) {
2542+
for (let i = 0; i < hooks.length; i++) hooks[i].active = false
2543+
}
2544+
}

packages/runtime-core/src/scheduler.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,11 @@ export function flushPostFlushCbs(seen?: CountMap) {
185185
postFlushIndex < activePostFlushCbs.length;
186186
postFlushIndex++
187187
) {
188-
if (
189-
__DEV__ &&
190-
checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex])
191-
) {
188+
const cb = activePostFlushCbs[postFlushIndex]
189+
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
192190
continue
193191
}
194-
activePostFlushCbs[postFlushIndex]()
192+
if (cb.active !== false) cb()
195193
}
196194
activePostFlushCbs = null
197195
postFlushIndex = 0

0 commit comments

Comments
 (0)