Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(runtime-core): do not fire lifecycle hooks during unmounting #9370

Merged
merged 11 commits into from
Jun 7, 2024
58 changes: 58 additions & 0 deletions packages/runtime-core/__tests__/apiLifecycle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {
KeepAlive,
TrackOpTypes,
h,
nextTick,
nodeOps,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
Expand Down Expand Up @@ -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)
})
})
3 changes: 0 additions & 3 deletions packages/runtime-core/src/apiLifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export type Component<

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
export type SetupContext<
Expand Down
4 changes: 4 additions & 0 deletions packages/runtime-core/src/components/KeepAlive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
type RendererElement,
type RendererInternals,
type RendererNode,
invalidateMount,
queuePostRenderEffect,
} from '../renderer'
import { setTransitionHooks } from './BaseTransition'
Expand Down Expand Up @@ -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) {
Expand Down
11 changes: 10 additions & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
type ComponentInternalInstance,
type ComponentOptions,
type Data,
type LifecycleHook,
createComponentInstance,
setupComponent,
} from './component'
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
}
8 changes: 3 additions & 5 deletions packages/runtime-core/src/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down