Skip to content

Commit 46cf6a6

Browse files
committed
refactor(keep-alive): refactor KeepAlive to use new fragment hooks.
1 parent d1e16c5 commit 46cf6a6

File tree

3 files changed

+108
-94
lines changed

3 files changed

+108
-94
lines changed

packages/runtime-core/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,3 +669,8 @@ export {
669669
checkTransitionMode,
670670
leaveCbKey,
671671
} from './components/BaseTransition'
672+
673+
/**
674+
* @internal
675+
*/
676+
export type { GenericComponent } from './component'

packages/runtime-vapor/src/components/KeepAlive.ts

Lines changed: 67 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import {
2+
type AsyncComponentInternalOptions,
3+
type GenericComponent,
24
type GenericComponentInstance,
35
type KeepAliveProps,
46
type VNode,
@@ -76,19 +78,26 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
7678
;(keepAliveInstance as any).__v_cache = cache
7779
}
7880

79-
function shouldCache(instance: VaporComponentInstance) {
81+
function shouldCache(
82+
block: GenericComponentInstance | VaporFragment,
83+
interop: boolean = false,
84+
) {
85+
const isAsync =
86+
!interop && isAsyncWrapper(block as GenericComponentInstance)
87+
const type = (
88+
interop
89+
? (block as VaporFragment).vnode!.type
90+
: (block as GenericComponentInstance).type
91+
) as GenericComponent & AsyncComponentInternalOptions
92+
8093
// For unresolved async wrappers, skip caching
8194
// Wait for resolution and re-process in createInnerComp
82-
if (isAsyncWrapper(instance) && !instance.type.__asyncResolved) {
95+
if (isAsync && !type.__asyncResolved) {
8396
return false
8497
}
8598

8699
const { include, exclude } = props
87-
const name = getComponentName(
88-
isAsyncWrapper(instance)
89-
? instance.type.__asyncResolved!
90-
: instance.type,
91-
)
100+
const name = getComponentName(isAsync ? type.__asyncResolved! : type)
92101
return !(
93102
(include && (!name || !matches(include, name))) ||
94103
(exclude && name && matches(exclude, name))
@@ -120,22 +129,12 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
120129
function cacheBlock() {
121130
// TODO suspense
122131
const block = keepAliveInstance.block!
123-
const innerBlock = getInnerBlock(block)!
124-
if (!innerBlock || !shouldCache(innerBlock)) return
125-
126-
let toCache: VaporComponentInstance | VaporFragment
127-
let key: CacheKey
128-
let frag: VaporFragment | undefined
129-
if (isFragment(block) && (frag = findInteropFragment(block))) {
130-
// vdom component: cache the fragment
131-
toCache = frag
132-
key = frag.vnode!.type
133-
} else {
134-
// vapor component: cache the instance
135-
toCache = innerBlock
136-
key = innerBlock.type
137-
}
138-
innerCacheBlock(key, toCache)
132+
const [innerBlock, interop] = getInnerBlock(block)!
133+
if (!innerBlock || !shouldCache(innerBlock, interop)) return
134+
innerCacheBlock(
135+
interop ? innerBlock.vnode!.type : innerBlock.type,
136+
innerBlock,
137+
)
139138
}
140139

141140
onMounted(cacheBlock)
@@ -170,52 +169,47 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
170169

171170
keepAliveInstance.getStorageContainer = () => storageContainer
172171

173-
keepAliveInstance.getCachedComponent = comp => {
174-
return cache.get(comp)
175-
}
172+
keepAliveInstance.getCachedComponent = comp => cache.get(comp)
176173

177174
keepAliveInstance.cacheComponent = (instance: VaporComponentInstance) => {
178-
if (!shouldCache(instance)) return
175+
if (!shouldCache(instance as GenericComponentInstance)) return
179176
instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
180177
innerCacheBlock(instance.type, instance)
181178
}
182179

183180
const processFragment = (frag: DynamicFragment) => {
184-
const innerBlock = getInnerBlock(frag.nodes)
185-
if (!innerBlock) return
181+
const [innerBlock, interop] = getInnerBlock(frag.nodes)
182+
if (!innerBlock && !shouldCache(innerBlock!, interop)) return
186183

187-
const fragment = findInteropFragment(frag.nodes)
188-
if (fragment) {
189-
if (cache.has(fragment.vnode!.type)) {
190-
fragment.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
184+
if (interop) {
185+
if (cache.has(innerBlock.vnode!.type)) {
186+
innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
191187
}
192-
if (shouldCache(innerBlock)) {
193-
fragment.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
188+
if (shouldCache(innerBlock!, true)) {
189+
innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
194190
}
195191
} else {
196-
if (cache.has(innerBlock.type)) {
197-
innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
192+
if (cache.has(innerBlock!.type)) {
193+
innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
198194
}
199-
if (shouldCache(innerBlock)) {
200-
innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
195+
if (shouldCache(innerBlock!)) {
196+
innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
201197
}
202198
}
203199
}
204200

205201
const cacheFragment = (fragment: DynamicFragment) => {
206-
const innerBlock = getInnerBlock(fragment.nodes)
207-
if (!innerBlock || !shouldCache(innerBlock)) return
202+
const [innerBlock, interop] = getInnerBlock(fragment.nodes)
203+
if (!innerBlock || !shouldCache(innerBlock, interop)) return
208204

209205
// Determine what to cache based on fragment type
210206
let toCache: VaporComponentInstance | VaporFragment
211207
let key: CacheKey
212208

213-
// find vdom interop fragment
214-
const frag = findInteropFragment(fragment.nodes)
215-
if (frag) {
216-
frag.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
217-
toCache = frag
218-
key = frag.vnode!.type
209+
if (interop) {
210+
innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
211+
toCache = innerBlock
212+
key = innerBlock.vnode!.type
219213
} else {
220214
innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
221215
toCache = innerBlock
@@ -253,33 +247,31 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
253247
return children
254248
}
255249

256-
// Process shapeFlag for vapor and vdom components
257-
// DynamicFragment (v-if, <component is/>) is processed in DynamicFragment.update
250+
// process shapeFlag
258251
if (isVaporComponent(children)) {
259252
children.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
260253
} else if (isInteropFragment(children)) {
261254
children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
262255
} else if (isDynamicFragment(children)) {
263-
children.hooks = {
264-
beforeUpdate(oldKey, newKey) {
256+
;(children.beforeTeardown || (children.beforeTeardown = [])).push(
257+
(oldKey, nodes, scope) => {
265258
processFragment(children)
266-
const scope = children.scope
267-
if (scope) {
268-
keptAliveScopes.set(oldKey, scope)
269-
}
270-
},
271-
afterUpdate(newKey, nodes, scope) {
272-
cacheFragment(children)
273-
},
274-
getScope(key) {
275-
const scope = keptAliveScopes.get(key)
276-
if (scope) {
277-
keptAliveScopes.delete(key)
278-
return scope
279-
}
259+
keptAliveScopes.set(oldKey, scope)
260+
return true
280261
},
262+
)
263+
;(children.beforeMount || (children.beforeMount = [])).push(() =>
264+
cacheFragment(children),
265+
)
266+
children.getScope = key => {
267+
const scope = keptAliveScopes.get(key)
268+
if (scope) {
269+
keptAliveScopes.delete(key)
270+
return scope
271+
}
281272
}
282-
cacheFragment(children)
273+
274+
processFragment(children)
283275
}
284276

285277
function pruneCache(filter: (name: string) => boolean) {
@@ -321,29 +313,26 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
321313
},
322314
})
323315

324-
function getInnerBlock(block: Block): VaporComponentInstance | undefined {
316+
type InnerBlockResult =
317+
| [VaporFragment, true]
318+
| [VaporComponentInstance, false]
319+
| [undefined, false]
320+
321+
function getInnerBlock(block: Block): InnerBlockResult {
325322
if (isVaporComponent(block)) {
326-
return block
323+
return [block, false]
327324
} else if (isInteropFragment(block)) {
328-
return block.vnode as any
325+
return [block, true]
329326
} else if (isFragment(block)) {
330327
return getInnerBlock(block.nodes)
331328
}
329+
return [undefined, false]
332330
}
333331

334332
function isInteropFragment(block: Block): block is VaporFragment {
335333
return !!(isFragment(block) && block.vnode)
336334
}
337335

338-
function findInteropFragment(block: Block): VaporFragment | undefined {
339-
if (isInteropFragment(block)) {
340-
return block
341-
}
342-
if (isFragment(block)) {
343-
return findInteropFragment(block.nodes)
344-
}
345-
}
346-
347336
function getInstanceFromCache(
348337
cached: VaporComponentInstance | VaporFragment,
349338
): GenericComponentInstance {

packages/runtime-vapor/src/fragment.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,6 @@ import {
2929
locateHydrationNode,
3030
} from './dom/hydration'
3131

32-
export interface FragmentHooks {
33-
getScope(key: any): EffectScope | undefined
34-
beforeUpdate(oldKey: any, newKey: any): void
35-
afterUpdate(newKey: any, nodes: Block, scope: EffectScope): void
36-
}
37-
3832
export class VaporFragment<T extends Block = Block>
3933
implements TransitionOptions
4034
{
@@ -75,7 +69,18 @@ export class DynamicFragment extends VaporFragment {
7569
current?: BlockFn
7670
fallback?: BlockFn
7771
anchorLabel?: string
78-
hooks?: FragmentHooks
72+
73+
// get the scope for the current key when used in keep-alive
74+
getScope?: (key: any) => EffectScope | undefined
75+
76+
// hooks
77+
beforeTeardown?: ((
78+
oldKey: any,
79+
nodes: Block,
80+
scope: EffectScope,
81+
) => boolean)[]
82+
beforeMount?: ((newKey: any, nodes: Block, scope: EffectScope) => void)[]
83+
mounted?: ((nodes: Block, scope: EffectScope) => void)[]
7984

8085
constructor(anchorLabel?: string) {
8186
super([])
@@ -100,9 +105,15 @@ export class DynamicFragment extends VaporFragment {
100105
const transition = this.$transition
101106
// teardown previous branch
102107
if (this.scope) {
103-
if (this.hooks) {
104-
this.hooks.beforeUpdate(this.current, key)
105-
} else {
108+
let preserveScope = false
109+
// if any of the hooks returns true the scope will be preserved
110+
// for kept-alive component
111+
if (this.beforeTeardown) {
112+
preserveScope = this.beforeTeardown.some(hook =>
113+
hook(this.current, this.nodes, this.scope!),
114+
)
115+
}
116+
if (!preserveScope) {
106117
this.scope.stop()
107118
}
108119
const mode = transition && transition.mode
@@ -156,8 +167,8 @@ export class DynamicFragment extends VaporFragment {
156167
parent: ParentNode | null,
157168
) {
158169
if (render) {
159-
// For KeepAlive, try to reuse the keepAlive scope for this key
160-
const scope = this.hooks && this.hooks.getScope(this.current)
170+
// try to reuse the kept-alive scope
171+
const scope = this.getScope && this.getScope(this.current)
161172
if (scope) {
162173
this.scope = scope
163174
} else {
@@ -166,13 +177,22 @@ export class DynamicFragment extends VaporFragment {
166177

167178
this.nodes = this.scope.run(render) || []
168179

169-
if (this.hooks) {
170-
this.hooks.afterUpdate(this.current, this.nodes, this.scope)
171-
}
172180
if (transition) {
173181
this.$transition = applyTransitionHooks(this.nodes, transition)
174182
}
175-
if (parent) insert(this.nodes, parent, this.anchor)
183+
184+
if (this.beforeMount) {
185+
this.beforeMount.forEach(hook =>
186+
hook(this.current, this.nodes, this.scope!),
187+
)
188+
}
189+
190+
if (parent) {
191+
insert(this.nodes, parent, this.anchor)
192+
if (this.mounted) {
193+
this.mounted.forEach(hook => hook(this.nodes, this.scope!))
194+
}
195+
}
176196
} else {
177197
this.scope = undefined
178198
this.nodes = []

0 commit comments

Comments
 (0)