]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): do not fire mount/activated hooks if unmounted before mounted...
authoredison <daiwei521@126.com>
Fri, 7 Jun 2024 05:48:50 +0000 (13:48 +0800)
committerGitHub <noreply@github.com>
Fri, 7 Jun 2024 05:48:50 +0000 (13:48 +0800)
close #8898
close #9264
close #9617

packages/runtime-core/__tests__/apiLifecycle.spec.ts
packages/runtime-core/src/apiLifecycle.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/components/KeepAlive.ts
packages/runtime-core/src/renderer.ts
packages/runtime-core/src/scheduler.ts

index 43054800afe88314cf25021e19980f0966f471a6..5a0da4d6789347aa83909c792851f8803df2e924 100644 (file)
@@ -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)
+  })
 })
index cfaf7636e9a223f08320fa441229d5f1f584f1d8..72873bc8a1b5cbd9442c3c4095b0a71cb62524b9 100644 (file)
@@ -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()
index a658f83db5b0287d91416846222ef71920e1db4e..f2ca046414247558d2fc263331a035b2c7104208 100644 (file)
@@ -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<
index 326c2f95dbb0ba4a7babd7eb81527955b9f31184..a2c8c2bf1a75834f36b5e1ef36d29d87f0dd15f7 100644 (file)
@@ -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) {
index cbb08da7bc710dffd511b8b756204ec733b545bb..d06c6858415ff8c62ddbe2918a58e1a06315312f 100644 (file)
@@ -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
+  }
+}
index b8d1445a183db551226294d68e65f7f0a1f3350d..4ae1c6d46e750b93eafbb1fe7269acb747867c90 100644 (file)
@@ -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