]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(Suspense): handle switching away from kept-alive component before resolve
authorEvan You <yyx990803@gmail.com>
Tue, 12 Dec 2023 15:50:28 +0000 (23:50 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 12 Dec 2023 15:50:28 +0000 (23:50 +0800)
close #6416
using test from #6467

packages/runtime-core/__tests__/components/Suspense.spec.ts
packages/runtime-core/src/components/Suspense.ts

index d647a96ecddad22948558540fbd86f7776dd43b7..60dcedc9700c46a1df10a5a31408378534167ea6 100644 (file)
@@ -19,7 +19,8 @@ import {
   shallowRef,
   SuspenseProps,
   resolveDynamicComponent,
-  Fragment
+  Fragment,
+  KeepAlive
 } from '@vue/runtime-test'
 import { createApp, defineComponent } from 'vue'
 import { type RawSlots } from 'packages/runtime-core/src/componentSlots'
@@ -1638,6 +1639,55 @@ describe('Suspense', () => {
     expect(serializeInner(root)).toBe(expected)
   })
 
+  // #6416
+  test('KeepAlive with Suspense', async () => {
+    const Async = defineAsyncComponent({
+      render() {
+        return h('div', 'async')
+      }
+    })
+    const Sync = {
+      render() {
+        return h('div', 'sync')
+      }
+    }
+    const components = [Async, Sync]
+    const viewRef = ref(0)
+    const root = nodeOps.createElement('div')
+    const App = {
+      render() {
+        return h(KeepAlive, null, {
+          default: () => {
+            return h(Suspense, null, {
+              default: h(components[viewRef.value]),
+              fallback: h('div', 'Loading-dynamic-components')
+            })
+          }
+        })
+      }
+    }
+    render(h(App), root)
+    expect(serializeInner(root)).toBe(`<div>Loading-dynamic-components</div>`)
+
+    viewRef.value = 1
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<div>sync</div>`)
+
+    viewRef.value = 0
+    await nextTick()
+
+    expect(serializeInner(root)).toBe('<!---->')
+
+    await Promise.all(deps)
+    await nextTick()
+    // when async resolve,it should be <div>async</div>
+    expect(serializeInner(root)).toBe('<div>async</div>')
+
+    viewRef.value = 1
+    await nextTick() //TypeError: Cannot read properties of null (reading 'parentNode'),This has been fixed
+    expect(serializeInner(root)).toBe(`<div>sync</div>`)
+  })
+
   describe('warnings', () => {
     // base function to check if a combination of slots warns or not
     function baseCheckWarn(
index 469dd87cbd9e0f44d84207724ef7521d825dbb8a..366f4a7bb03c625426c1a3597976b6c40cb51c1d 100644 (file)
@@ -47,6 +47,9 @@ export interface SuspenseProps {
 
 export const isSuspense = (type: any): boolean => type.__isSuspense
 
+// incrementing unique id for every pending branch
+let suspenseId = 0
+
 // Suspense exposes a component-like API, and is treated like a component
 // in the compiler, but internally it's a special built-in type that hooks
 // directly into the renderer.
@@ -249,7 +252,8 @@ function patchSuspense(
       }
     } else {
       // toggled before pending tree is resolved
-      suspense.pendingId++
+      // increment pending ID. this is used to invalidate async callbacks
+      suspense.pendingId = suspenseId++
       if (isHydrating) {
         // if toggled before hydration is finished, the current DOM tree is
         // no longer valid. set it as the active branch so it will be unmounted
@@ -259,7 +263,6 @@ function patchSuspense(
       } else {
         unmount(pendingBranch, parentComponent, suspense)
       }
-      // increment pending ID. this is used to invalidate async callbacks
       // reset suspense state
       suspense.deps = 0
       // discard effects from pending branch
@@ -350,7 +353,11 @@ function patchSuspense(
       triggerEvent(n2, 'onPending')
       // mount pending branch in off-dom container
       suspense.pendingBranch = newBranch
-      suspense.pendingId++
+      if (newBranch.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
+        suspense.pendingId = newBranch.component!.suspenseId!
+      } else {
+        suspense.pendingId = suspenseId++
+      }
       patch(
         null,
         newBranch,