From: Evan You Date: Tue, 12 Dec 2023 15:50:28 +0000 (+0800) Subject: fix(Suspense): handle switching away from kept-alive component before resolve X-Git-Tag: v3.4.0-beta.1~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aa0c13f637df7eb27faa2545ee731f543c0813ec;p=thirdparty%2Fvuejs%2Fcore.git fix(Suspense): handle switching away from kept-alive component before resolve close #6416 using test from #6467 --- diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index d647a96ecd..60dcedc970 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -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(`
Loading-dynamic-components
`) + + viewRef.value = 1 + await nextTick() + expect(serializeInner(root)).toBe(`
sync
`) + + viewRef.value = 0 + await nextTick() + + expect(serializeInner(root)).toBe('') + + await Promise.all(deps) + await nextTick() + // when async resolve,it should be
async
+ expect(serializeInner(root)).toBe('
async
') + + viewRef.value = 1 + await nextTick() //TypeError: Cannot read properties of null (reading 'parentNode'),This has been fixed + expect(serializeInner(root)).toBe(`
sync
`) + }) + describe('warnings', () => { // base function to check if a combination of slots warns or not function baseCheckWarn( diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index 469dd87cbd..366f4a7bb0 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -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,