Kaynağa Gözat

chore: improve

daiwei 1 hafta önce
ebeveyn
işleme
2160fc0c87

+ 47 - 0
packages/runtime-core/__tests__/components/KeepAlive.spec.ts

@@ -500,6 +500,53 @@ describe('KeepAlive', () => {
     expect(mountedB).toHaveBeenCalledTimes(1)
   })
 
+  test('should not replay a deferred update when a newer child job is already queued', async () => {
+    let renders = 0
+    const visible = ref(true)
+    const value = ref('A')
+
+    const Home = defineComponent({
+      name: 'Home',
+      setup() {
+        return () => {
+          renders++
+          return h('main', value.value)
+        }
+      },
+    })
+
+    const App = defineComponent({
+      setup() {
+        return () => h(KeepAlive, null, [visible.value ? h(Home) : null])
+      },
+    })
+
+    render(h(App), root)
+    expect(serializeInner(root)).toBe(`<main>A</main>`)
+    expect(renders).toBe(1)
+
+    visible.value = false
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<!---->`)
+
+    value.value = 'B'
+    await nextTick()
+    expect(renders).toBe(1)
+
+    const queueNewerUpdate = vi.fn(() => {
+      value.value = 'C'
+    }) as any
+    queueNewerUpdate.id = -1
+
+    visible.value = true
+    queuePostFlushCb(queueNewerUpdate)
+    await nextTick()
+
+    expect(queueNewerUpdate).toHaveBeenCalledTimes(1)
+    expect(serializeInner(root)).toBe(`<main>C</main>`)
+    expect(renders).toBe(2)
+  })
+
   async function assertNameMatch(props: KeepAliveProps) {
     const outerRef = ref(true)
     const viewRef = ref('one')

+ 6 - 0
packages/runtime-core/src/component.ts

@@ -520,6 +520,11 @@ export interface ComponentInternalInstance {
   isMounted: boolean
   isUnmounted: boolean
   isDeactivated: boolean
+  /**
+   * KeepAlive deferred update replay job.
+   * @internal
+   */
+  keepAliveReplayJob: SchedulerJob | null
   /**
    * @internal
    */
@@ -684,6 +689,7 @@ export function createComponentInstance(
     isMounted: false,
     isUnmounted: false,
     isDeactivated: false,
+    keepAliveReplayJob: null,
     bc: null,
     c: null,
     bm: null,

+ 6 - 9
packages/runtime-core/src/components/KeepAlive.ts

@@ -43,7 +43,7 @@ import {
   queuePostRenderEffect,
   setKeepAliveBranchActive,
 } from '../renderer'
-import { type SchedulerJob, queueJob, queuePostFlushCb } from '../scheduler'
+import { queueJob, queuePostFlushCb } from '../scheduler'
 import { setTransitionHooks } from './BaseTransition'
 import type { ComponentRenderContext } from '../componentPublicInstance'
 import { devtoolsComponentAdded } from '../devtools'
@@ -153,16 +153,13 @@ const KeepAliveImpl: ComponentOptions = {
         optimized,
       )
       if (updates) {
-        // Replay deferred child updates through the scheduler after the branch
-        // is active again so parent jobs can still flip the branch back to
-        // inactive before child updates run.
+        // Replay deferred child updates in a later scheduler turn so parent
+        // jobs can deactivate the branch again first. The replay job also
+        // bails if a normal update for the same instance is already queued.
         queuePostFlushCb(() => {
           for (const pending of updates) {
-            if (!pending.isUnmounted) {
-              const job = (() => pending.update()) as SchedulerJob
-              job.id = pending.uid
-              job.i = pending
-              queueJob(job)
+            if (pending.keepAliveReplayJob) {
+              queueJob(pending.keepAliveReplayJob)
             }
           }
           updates.clear()

+ 22 - 0
packages/runtime-core/src/renderer.ts

@@ -1474,6 +1474,7 @@ function baseCreateRenderer(
         if (deferKeepAliveBranchUpdate(instance)) {
           return
         }
+        instance.keepAliveReplayJob = null
 
         if (__FEATURE_SUSPENSE__) {
           const nonHydratedAsyncRoot = locateNonHydratedAsyncRoot(instance)
@@ -2614,6 +2615,7 @@ export function deferKeepAliveBranchUpdate(
     const updates = deferredKeepAliveBranchUpdates.get(current)
     if (updates) {
       updates.add(instance)
+      instance.keepAliveReplayJob ||= createKeepAliveReplayJob(instance)
       return true
     }
     // Nested KeepAlive roots manage their own inactive branches.
@@ -2625,6 +2627,26 @@ export function deferKeepAliveBranchUpdate(
   return false
 }
 
+function createKeepAliveReplayJob(
+  instance: ComponentInternalInstance,
+): SchedulerJob {
+  const job = (() => {
+    if (instance.isUnmounted || instance.keepAliveReplayJob !== job) {
+      return
+    }
+    if (instance.job.flags! & SchedulerJobFlags.QUEUED) {
+      return
+    }
+    if (!deferKeepAliveBranchUpdate(instance)) {
+      instance.keepAliveReplayJob = null
+      instance.update()
+    }
+  }) as SchedulerJob
+  job.id = instance.uid
+  job.i = instance
+  return job
+}
+
 export function setKeepAliveBranchActive(
   instance: ComponentInternalInstance,
   active: boolean,