diff --git a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
index 4cd8727f0..ae0bf090e 100644
--- a/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
+++ b/packages/runtime-vapor/__tests__/components/KeepAlive.spec.ts
@@ -7,7 +7,7 @@ import {
onUnmounted,
ref,
} from 'vue'
-import type { VaporComponent } from '../../src/component'
+import type { LooseRawProps, VaporComponent } from '../../src/component'
import { makeRender } from '../_utils'
import { VaporKeepAliveImpl as VaporKeepAlive } from '../../src/components/KeepAlive'
import {
@@ -70,6 +70,13 @@ describe('VaporKeepAlive', () => {
return n0
},
})
+ oneTestHooks = {
+ beforeMount: vi.fn(),
+ mounted: vi.fn(),
+ activated: vi.fn(),
+ deactivated: vi.fn(),
+ unmounted: vi.fn(),
+ }
oneTest = defineVaporComponent({
name: 'oneTest',
setup() {
@@ -469,4 +476,215 @@ describe('VaporKeepAlive', () => {
assertHookCalls(oneHooks, [1, 1, 4, 3, 0])
assertHookCalls(twoHooks, [1, 1, 4, 4, 0]) // should remain inactive
})
+
+ async function assertNameMatch(props: LooseRawProps) {
+ const outerRef = ref(true)
+ const viewRef = ref('one')
+ const { html } = define({
+ setup() {
+ return createIf(
+ () => outerRef.value,
+ () =>
+ createComponent(VaporKeepAlive, props, {
+ default: () => createDynamicComponent(() => views[viewRef.value]),
+ }),
+ )
+ },
+ }).render()
+
+ expect(html()).toBe(`
one
`)
+ assertHookCalls(oneHooks, [1, 1, 1, 0, 0])
+ assertHookCalls(twoHooks, [0, 0, 0, 0, 0])
+
+ viewRef.value = 'two'
+ await nextTick()
+ expect(html()).toBe(`two
`)
+ assertHookCalls(oneHooks, [1, 1, 1, 1, 0])
+ assertHookCalls(twoHooks, [1, 1, 0, 0, 0])
+
+ viewRef.value = 'one'
+ await nextTick()
+ expect(html()).toBe(`one
`)
+ assertHookCalls(oneHooks, [1, 1, 2, 1, 0])
+ assertHookCalls(twoHooks, [1, 1, 0, 0, 1])
+
+ viewRef.value = 'two'
+ await nextTick()
+ expect(html()).toBe(`two
`)
+ assertHookCalls(oneHooks, [1, 1, 2, 2, 0])
+ assertHookCalls(twoHooks, [2, 2, 0, 0, 1])
+
+ // teardown
+ outerRef.value = false
+ await nextTick()
+ expect(html()).toBe(``)
+ assertHookCalls(oneHooks, [1, 1, 2, 2, 1])
+ assertHookCalls(twoHooks, [2, 2, 0, 0, 2])
+ }
+
+ async function assertNameMatchWithFlag(props: LooseRawProps) {
+ const outerRef = ref(true)
+ const viewRef = ref('one')
+ const { html } = define({
+ setup() {
+ return createIf(
+ () => outerRef.value,
+ () =>
+ createComponent(VaporKeepAlive, props, {
+ default: () => createDynamicComponent(() => views[viewRef.value]),
+ }),
+ )
+ },
+ }).render()
+
+ expect(html()).toBe(`one
`)
+ assertHookCalls(oneHooks, [1, 1, 1, 0, 0])
+ assertHookCalls(oneTestHooks, [0, 0, 0, 0, 0])
+ assertHookCalls(twoHooks, [0, 0, 0, 0, 0])
+
+ viewRef.value = 'oneTest'
+ await nextTick()
+ expect(html()).toBe(`oneTest
`)
+ assertHookCalls(oneHooks, [1, 1, 1, 1, 0])
+ assertHookCalls(oneTestHooks, [1, 1, 1, 0, 0])
+ assertHookCalls(twoHooks, [0, 0, 0, 0, 0])
+
+ viewRef.value = 'two'
+ await nextTick()
+ expect(html()).toBe(`two
`)
+ assertHookCalls(oneHooks, [1, 1, 1, 1, 0])
+ assertHookCalls(oneTestHooks, [1, 1, 1, 1, 0])
+ assertHookCalls(twoHooks, [1, 1, 0, 0, 0])
+
+ viewRef.value = 'one'
+ await nextTick()
+ expect(html()).toBe(`one
`)
+ assertHookCalls(oneHooks, [1, 1, 2, 1, 0])
+ assertHookCalls(oneTestHooks, [1, 1, 1, 1, 0])
+ assertHookCalls(twoHooks, [1, 1, 0, 0, 1])
+
+ viewRef.value = 'oneTest'
+ await nextTick()
+ expect(html()).toBe(`oneTest
`)
+ assertHookCalls(oneHooks, [1, 1, 2, 2, 0])
+ assertHookCalls(oneTestHooks, [1, 1, 2, 1, 0])
+ assertHookCalls(twoHooks, [1, 1, 0, 0, 1])
+
+ viewRef.value = 'two'
+ await nextTick()
+ expect(html()).toBe(`two
`)
+ assertHookCalls(oneHooks, [1, 1, 2, 2, 0])
+ assertHookCalls(oneTestHooks, [1, 1, 2, 2, 0])
+ assertHookCalls(twoHooks, [2, 2, 0, 0, 1])
+
+ // teardown
+ outerRef.value = false
+ await nextTick()
+ expect(html()).toBe(``)
+ assertHookCalls(oneHooks, [1, 1, 2, 2, 1])
+ assertHookCalls(oneTestHooks, [1, 1, 2, 2, 1])
+ assertHookCalls(twoHooks, [2, 2, 0, 0, 2])
+ }
+
+ async function assertNameMatchWithFlagExclude(props: LooseRawProps) {
+ const outerRef = ref(true)
+ const viewRef = ref('one')
+ const { html } = define({
+ setup() {
+ return createIf(
+ () => outerRef.value,
+ () =>
+ createComponent(VaporKeepAlive, props, {
+ default: () => createDynamicComponent(() => views[viewRef.value]),
+ }),
+ )
+ },
+ }).render()
+
+ expect(html()).toBe(`one
`)
+ assertHookCalls(oneHooks, [1, 1, 0, 0, 0])
+ assertHookCalls(oneTestHooks, [0, 0, 0, 0, 0])
+ assertHookCalls(twoHooks, [0, 0, 0, 0, 0])
+
+ viewRef.value = 'oneTest'
+ await nextTick()
+ expect(html()).toBe(`oneTest
`)
+ assertHookCalls(oneHooks, [1, 1, 0, 0, 1])
+ assertHookCalls(oneTestHooks, [1, 1, 0, 0, 0])
+ assertHookCalls(twoHooks, [0, 0, 0, 0, 0])
+
+ viewRef.value = 'two'
+ await nextTick()
+ expect(html()).toBe(`two
`)
+ assertHookCalls(oneHooks, [1, 1, 0, 0, 1])
+ assertHookCalls(oneTestHooks, [1, 1, 0, 0, 1])
+ assertHookCalls(twoHooks, [1, 1, 1, 0, 0])
+
+ viewRef.value = 'one'
+ await nextTick()
+ expect(html()).toBe(`one
`)
+ assertHookCalls(oneHooks, [2, 2, 0, 0, 1])
+ assertHookCalls(oneTestHooks, [1, 1, 0, 0, 1])
+ assertHookCalls(twoHooks, [1, 1, 1, 1, 0])
+
+ viewRef.value = 'oneTest'
+ await nextTick()
+ expect(html()).toBe(`oneTest
`)
+ assertHookCalls(oneHooks, [2, 2, 0, 0, 2])
+ assertHookCalls(oneTestHooks, [2, 2, 0, 0, 1])
+ assertHookCalls(twoHooks, [1, 1, 1, 1, 0])
+
+ viewRef.value = 'two'
+ await nextTick()
+ expect(html()).toBe(`two
`)
+ assertHookCalls(oneHooks, [2, 2, 0, 0, 2])
+ assertHookCalls(oneTestHooks, [2, 2, 0, 0, 2])
+ assertHookCalls(twoHooks, [1, 1, 2, 1, 0])
+
+ // teardown
+ outerRef.value = false
+ await nextTick()
+ expect(html()).toBe(``)
+ assertHookCalls(oneHooks, [2, 2, 0, 0, 2])
+ assertHookCalls(oneTestHooks, [2, 2, 0, 0, 2])
+ assertHookCalls(twoHooks, [1, 1, 2, 2, 1])
+ }
+
+ describe('props', () => {
+ test('include (string)', async () => {
+ await assertNameMatch({ include: () => 'one' })
+ })
+
+ test('include (regex)', async () => {
+ await assertNameMatch({ include: () => /^one$/ })
+ })
+
+ test('include (regex with g flag)', async () => {
+ await assertNameMatchWithFlag({ include: () => /one/g })
+ })
+
+ test('include (array)', async () => {
+ await assertNameMatch({ include: () => ['one'] })
+ })
+
+ test('exclude (string)', async () => {
+ await assertNameMatch({ exclude: () => 'two' })
+ })
+
+ test('exclude (regex)', async () => {
+ await assertNameMatch({ exclude: () => /^two$/ })
+ })
+
+ test('exclude (regex with a flag)', async () => {
+ await assertNameMatchWithFlagExclude({ exclude: () => /one/g })
+ })
+
+ test('exclude (array)', async () => {
+ await assertNameMatch({ exclude: () => ['two'] })
+ })
+
+ test('include + exclude', async () => {
+ await assertNameMatch({ include: () => 'one,two', exclude: () => 'two' })
+ })
+ })
})
diff --git a/packages/runtime-vapor/src/components/KeepAlive.ts b/packages/runtime-vapor/src/components/KeepAlive.ts
index f40a508a5..45844180a 100644
--- a/packages/runtime-vapor/src/components/KeepAlive.ts
+++ b/packages/runtime-vapor/src/components/KeepAlive.ts
@@ -64,13 +64,21 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
const { include, exclude, max } = props
+ function shouldCache(instance: VaporComponentInstance) {
+ const name = getComponentName(instance.type)
+ return !(
+ (include && (!name || !matches(include, name))) ||
+ (exclude && name && matches(exclude, name))
+ )
+ }
+
function cacheBlock() {
// TODO suspense
const currentBlock = keepAliveInstance.block!
if (!isValidBlock(currentBlock)) return
const block = getInnerBlock(currentBlock)!
- if (!block) return
+ if (!block || !shouldCache(block)) return
const key = block.type
if (cache.has(key)) {
@@ -111,13 +119,16 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
instance.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
}
- const name = getComponentName(instance.type)
- if (
- !(
- (include && (!name || !matches(include, name))) ||
- (exclude && name && matches(exclude, name))
- )
- ) {
+ // const name = getComponentName(instance.type)
+ // if (
+ // !(
+ // (include && (!name || !matches(include, name))) ||
+ // (exclude && name && matches(exclude, name))
+ // )
+ // ) {
+ // instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
+ // }
+ if (shouldCache(instance)) {
instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
}
}