|
|
@@ -1,4 +1,6 @@
|
|
|
import {
|
|
|
+ type AsyncComponentInternalOptions,
|
|
|
+ type GenericComponent,
|
|
|
type GenericComponentInstance,
|
|
|
type KeepAliveProps,
|
|
|
type VNode,
|
|
|
@@ -29,8 +31,10 @@ import { createElement } from '../dom/node'
|
|
|
import {
|
|
|
type DynamicFragment,
|
|
|
type VaporFragment,
|
|
|
+ isDynamicFragment,
|
|
|
isFragment,
|
|
|
} from '../fragment'
|
|
|
+import type { EffectScope } from '@vue/reactivity'
|
|
|
|
|
|
export interface KeepAliveInstance extends VaporComponentInstance {
|
|
|
activate: (
|
|
|
@@ -39,13 +43,10 @@ export interface KeepAliveInstance extends VaporComponentInstance {
|
|
|
anchor?: Node | null | 0,
|
|
|
) => void
|
|
|
deactivate: (instance: VaporComponentInstance) => void
|
|
|
- cacheComponent: (instance: VaporComponentInstance) => void
|
|
|
getCachedComponent: (
|
|
|
comp: VaporComponent,
|
|
|
) => VaporComponentInstance | VaporFragment | undefined
|
|
|
getStorageContainer: () => ParentNode
|
|
|
- processFragment: (fragment: DynamicFragment) => void
|
|
|
- cacheFragment: (fragment: DynamicFragment) => void
|
|
|
}
|
|
|
|
|
|
type CacheKey = VaporComponent | VNode['type']
|
|
|
@@ -69,35 +70,31 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
|
|
|
const cache: Cache = new Map()
|
|
|
const keys: Keys = new Set()
|
|
|
const storageContainer = createElement('div')
|
|
|
+ const keptAliveScopes = new Map<any, EffectScope>()
|
|
|
let current: VaporComponentInstance | VaporFragment | undefined
|
|
|
|
|
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
|
|
;(keepAliveInstance as any).__v_cache = cache
|
|
|
}
|
|
|
|
|
|
- function shouldCache(instance: VaporComponentInstance) {
|
|
|
- // For unresolved async wrappers, skip caching
|
|
|
- // Wait for resolution and re-process in createInnerComp
|
|
|
- if (isAsyncWrapper(instance) && !instance.type.__asyncResolved) {
|
|
|
- return false
|
|
|
- }
|
|
|
+ keepAliveInstance.getStorageContainer = () => storageContainer
|
|
|
|
|
|
- const { include, exclude } = props
|
|
|
- const name = getComponentName(
|
|
|
- isAsyncWrapper(instance)
|
|
|
- ? instance.type.__asyncResolved!
|
|
|
- : instance.type,
|
|
|
- )
|
|
|
- return !(
|
|
|
- (include && (!name || !matches(include, name))) ||
|
|
|
- (exclude && name && matches(exclude, name))
|
|
|
- )
|
|
|
+ keepAliveInstance.getCachedComponent = comp => cache.get(comp)
|
|
|
+
|
|
|
+ keepAliveInstance.activate = (instance, parentNode, anchor) => {
|
|
|
+ current = instance
|
|
|
+ activate(instance, parentNode, anchor)
|
|
|
+ }
|
|
|
+
|
|
|
+ keepAliveInstance.deactivate = instance => {
|
|
|
+ current = undefined
|
|
|
+ deactivate(instance, storageContainer)
|
|
|
}
|
|
|
|
|
|
- function innerCacheBlock(
|
|
|
+ const innerCacheBlock = (
|
|
|
key: CacheKey,
|
|
|
instance: VaporComponentInstance | VaporFragment,
|
|
|
- ) {
|
|
|
+ ) => {
|
|
|
const { max } = props
|
|
|
|
|
|
if (cache.has(key)) {
|
|
|
@@ -116,149 +113,54 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
|
|
|
current = instance
|
|
|
}
|
|
|
|
|
|
- function cacheBlock() {
|
|
|
+ const cacheBlock = () => {
|
|
|
// TODO suspense
|
|
|
const block = keepAliveInstance.block!
|
|
|
- const innerBlock = getInnerBlock(block)!
|
|
|
- if (!innerBlock || !shouldCache(innerBlock)) return
|
|
|
-
|
|
|
- let toCache: VaporComponentInstance | VaporFragment
|
|
|
- let key: CacheKey
|
|
|
- let frag: VaporFragment | undefined
|
|
|
- if (isFragment(block) && (frag = findInteropFragment(block))) {
|
|
|
- // vdom component: cache the fragment
|
|
|
- toCache = frag
|
|
|
- key = frag.vnode!.type
|
|
|
- } else {
|
|
|
- // vapor component: cache the instance
|
|
|
- toCache = innerBlock
|
|
|
- key = innerBlock.type
|
|
|
- }
|
|
|
- innerCacheBlock(key, toCache)
|
|
|
- }
|
|
|
-
|
|
|
- onMounted(cacheBlock)
|
|
|
- onUpdated(cacheBlock)
|
|
|
-
|
|
|
- onBeforeUnmount(() => {
|
|
|
- cache.forEach((cached, key) => {
|
|
|
- const instance = getInstanceFromCache(cached)
|
|
|
- if (!instance) return
|
|
|
-
|
|
|
- resetCachedShapeFlag(cached)
|
|
|
- cache.delete(key)
|
|
|
-
|
|
|
- // current instance will be unmounted as part of keep-alive's unmount
|
|
|
- if (current) {
|
|
|
- const currentKey = isVaporComponent(current)
|
|
|
- ? current.type
|
|
|
- : current.vnode!.type
|
|
|
- if (currentKey === key) {
|
|
|
- // call deactivated hook
|
|
|
- const da = instance.da
|
|
|
- da && queuePostFlushCb(da)
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- remove(cached, storageContainer)
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- keepAliveInstance.getStorageContainer = () => storageContainer
|
|
|
-
|
|
|
- keepAliveInstance.getCachedComponent = comp => {
|
|
|
- return cache.get(comp)
|
|
|
- }
|
|
|
-
|
|
|
- keepAliveInstance.cacheComponent = (instance: VaporComponentInstance) => {
|
|
|
- if (!shouldCache(instance)) return
|
|
|
- instance.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
- innerCacheBlock(instance.type, instance)
|
|
|
+ const [innerBlock, interop] = getInnerBlock(block)!
|
|
|
+ if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
|
|
|
+ innerCacheBlock(
|
|
|
+ interop ? innerBlock.vnode!.type : innerBlock.type,
|
|
|
+ innerBlock,
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
- keepAliveInstance.processFragment = (frag: DynamicFragment) => {
|
|
|
- const innerBlock = getInnerBlock(frag.nodes)
|
|
|
- if (!innerBlock) return
|
|
|
+ const processFragment = (frag: DynamicFragment) => {
|
|
|
+ const [innerBlock, interop] = getInnerBlock(frag.nodes)
|
|
|
+ if (!innerBlock && !shouldCache(innerBlock!, props, interop)) return
|
|
|
|
|
|
- const fragment = findInteropFragment(frag.nodes)
|
|
|
- if (fragment) {
|
|
|
- if (cache.has(fragment.vnode!.type)) {
|
|
|
- fragment.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
|
|
|
+ if (interop) {
|
|
|
+ if (cache.has(innerBlock.vnode!.type)) {
|
|
|
+ innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
|
|
|
}
|
|
|
- if (shouldCache(innerBlock)) {
|
|
|
- fragment.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
+ if (shouldCache(innerBlock!, props, true)) {
|
|
|
+ innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
}
|
|
|
} else {
|
|
|
- if (cache.has(innerBlock.type)) {
|
|
|
- innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
|
|
|
+ if (cache.has(innerBlock!.type)) {
|
|
|
+ innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_KEPT_ALIVE
|
|
|
}
|
|
|
- if (shouldCache(innerBlock)) {
|
|
|
- innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
+ if (shouldCache(innerBlock!, props)) {
|
|
|
+ innerBlock!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- keepAliveInstance.cacheFragment = (fragment: DynamicFragment) => {
|
|
|
- const innerBlock = getInnerBlock(fragment.nodes)
|
|
|
- if (!innerBlock || !shouldCache(innerBlock)) return
|
|
|
+ const cacheFragment = (fragment: DynamicFragment) => {
|
|
|
+ const [innerBlock, interop] = getInnerBlock(fragment.nodes)
|
|
|
+ if (!innerBlock || !shouldCache(innerBlock, props, interop)) return
|
|
|
|
|
|
- // Determine what to cache based on fragment type
|
|
|
- let toCache: VaporComponentInstance | VaporFragment
|
|
|
let key: CacheKey
|
|
|
-
|
|
|
- // find vdom interop fragment
|
|
|
- const frag = findInteropFragment(fragment)
|
|
|
- if (frag) {
|
|
|
- frag.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
- toCache = frag
|
|
|
- key = frag.vnode!.type
|
|
|
+ if (interop) {
|
|
|
+ innerBlock.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
+ key = innerBlock.vnode!.type
|
|
|
} else {
|
|
|
innerBlock.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
- toCache = innerBlock
|
|
|
key = innerBlock.type
|
|
|
}
|
|
|
-
|
|
|
- innerCacheBlock(key, toCache)
|
|
|
+ innerCacheBlock(key, innerBlock)
|
|
|
}
|
|
|
|
|
|
- keepAliveInstance.activate = (instance, parentNode, anchor) => {
|
|
|
- current = instance
|
|
|
- activate(instance, parentNode, anchor)
|
|
|
- }
|
|
|
-
|
|
|
- keepAliveInstance.deactivate = instance => {
|
|
|
- current = undefined
|
|
|
- deactivate(instance, storageContainer)
|
|
|
- }
|
|
|
-
|
|
|
- function resetCachedShapeFlag(
|
|
|
- cached: VaporComponentInstance | VaporFragment,
|
|
|
- ) {
|
|
|
- if (isVaporComponent(cached)) {
|
|
|
- resetShapeFlag(cached)
|
|
|
- } else {
|
|
|
- resetShapeFlag(cached.vnode)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- let children = slots.default()
|
|
|
- if (isArray(children) && children.length > 1) {
|
|
|
- if (__DEV__) {
|
|
|
- warn(`KeepAlive should contain exactly one component child.`)
|
|
|
- }
|
|
|
- return children
|
|
|
- }
|
|
|
-
|
|
|
- // Process shapeFlag for vapor and vdom components
|
|
|
- // DynamicFragment (v-if, <component is/>) is processed in DynamicFragment.update
|
|
|
- if (isVaporComponent(children)) {
|
|
|
- children.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
- } else if (isInteropFragment(children)) {
|
|
|
- children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
- }
|
|
|
-
|
|
|
- function pruneCache(filter: (name: string) => boolean) {
|
|
|
+ const pruneCache = (filter: (name: string) => boolean) => {
|
|
|
cache.forEach((cached, key) => {
|
|
|
const instance = getInstanceFromCache(cached)
|
|
|
if (!instance) return
|
|
|
@@ -269,7 +171,7 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- function pruneCacheEntry(key: CacheKey) {
|
|
|
+ const pruneCacheEntry = (key: CacheKey) => {
|
|
|
const cached = cache.get(key)!
|
|
|
|
|
|
resetCachedShapeFlag(cached)
|
|
|
@@ -293,33 +195,142 @@ export const VaporKeepAliveImpl: ObjectVaporComponent = defineVaporComponent({
|
|
|
{ flush: 'post', deep: true },
|
|
|
)
|
|
|
|
|
|
+ onMounted(cacheBlock)
|
|
|
+ onUpdated(cacheBlock)
|
|
|
+ onBeforeUnmount(() => {
|
|
|
+ cache.forEach((cached, key) => {
|
|
|
+ const instance = getInstanceFromCache(cached)
|
|
|
+ if (!instance) return
|
|
|
+
|
|
|
+ resetCachedShapeFlag(cached)
|
|
|
+ cache.delete(key)
|
|
|
+
|
|
|
+ // current instance will be unmounted as part of keep-alive's unmount
|
|
|
+ if (current) {
|
|
|
+ const currentKey = isVaporComponent(current)
|
|
|
+ ? current.type
|
|
|
+ : current.vnode!.type
|
|
|
+ if (currentKey === key) {
|
|
|
+ // call deactivated hook
|
|
|
+ const da = instance.da
|
|
|
+ da && queuePostFlushCb(da)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ remove(cached, storageContainer)
|
|
|
+ })
|
|
|
+ keptAliveScopes.forEach(scope => scope.stop())
|
|
|
+ keptAliveScopes.clear()
|
|
|
+ })
|
|
|
+
|
|
|
+ let children = slots.default()
|
|
|
+ if (isArray(children)) {
|
|
|
+ children = children.filter(child => !(child instanceof Comment))
|
|
|
+ if (children.length > 1) {
|
|
|
+ if (__DEV__) {
|
|
|
+ warn(`KeepAlive should contain exactly one component child.`)
|
|
|
+ }
|
|
|
+ return children
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // inject hooks to DynamicFragment to cache components during updates
|
|
|
+ const injectKeepAliveHooks = (frag: DynamicFragment) => {
|
|
|
+ ;(frag.beforeTeardown || (frag.beforeTeardown = [])).push(
|
|
|
+ (oldKey, nodes, scope) => {
|
|
|
+ processFragment(frag)
|
|
|
+ keptAliveScopes.set(oldKey, scope)
|
|
|
+ return true
|
|
|
+ },
|
|
|
+ )
|
|
|
+ ;(frag.beforeMount || (frag.beforeMount = [])).push(() =>
|
|
|
+ cacheFragment(frag),
|
|
|
+ )
|
|
|
+ frag.getScope = key => {
|
|
|
+ const scope = keptAliveScopes.get(key)
|
|
|
+ if (scope) {
|
|
|
+ keptAliveScopes.delete(key)
|
|
|
+ return scope
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // process shapeFlag
|
|
|
+ if (isVaporComponent(children)) {
|
|
|
+ children.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
+ if (isAsyncWrapper(children)) {
|
|
|
+ injectKeepAliveHooks(children.block as DynamicFragment)
|
|
|
+ }
|
|
|
+ } else if (isInteropFragment(children)) {
|
|
|
+ children.vnode!.shapeFlag! |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
|
|
|
+ } else if (isDynamicFragment(children)) {
|
|
|
+ processFragment(children)
|
|
|
+ injectKeepAliveHooks(children)
|
|
|
+ if (isVaporComponent(children.nodes) && isAsyncWrapper(children.nodes)) {
|
|
|
+ injectKeepAliveHooks(children.nodes.block as DynamicFragment)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return children
|
|
|
},
|
|
|
})
|
|
|
|
|
|
-function getInnerBlock(block: Block): VaporComponentInstance | undefined {
|
|
|
+const shouldCache = (
|
|
|
+ block: GenericComponentInstance | VaporFragment,
|
|
|
+ props: KeepAliveProps,
|
|
|
+ interop: boolean = false,
|
|
|
+) => {
|
|
|
+ const isAsync = !interop && isAsyncWrapper(block as GenericComponentInstance)
|
|
|
+ const type = (
|
|
|
+ interop
|
|
|
+ ? (block as VaporFragment).vnode!.type
|
|
|
+ : (block as GenericComponentInstance).type
|
|
|
+ ) as GenericComponent & AsyncComponentInternalOptions
|
|
|
+
|
|
|
+ // return true to ensure hooks are injected into its block (DynamicFragment)
|
|
|
+ if (isAsync && !type.__asyncResolved) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ const { include, exclude } = props
|
|
|
+ const name = getComponentName(isAsync ? type.__asyncResolved! : type)
|
|
|
+ return !(
|
|
|
+ (include && (!name || !matches(include, name))) ||
|
|
|
+ (exclude && name && matches(exclude, name))
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const resetCachedShapeFlag = (
|
|
|
+ cached: VaporComponentInstance | VaporFragment,
|
|
|
+) => {
|
|
|
+ if (isVaporComponent(cached)) {
|
|
|
+ resetShapeFlag(cached)
|
|
|
+ } else {
|
|
|
+ resetShapeFlag(cached.vnode)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type InnerBlockResult =
|
|
|
+ | [VaporFragment, true]
|
|
|
+ | [VaporComponentInstance, false]
|
|
|
+ | [undefined, false]
|
|
|
+
|
|
|
+function getInnerBlock(block: Block): InnerBlockResult {
|
|
|
if (isVaporComponent(block)) {
|
|
|
- return block
|
|
|
+ return [block, false]
|
|
|
} else if (isInteropFragment(block)) {
|
|
|
- return block.vnode as any
|
|
|
+ return [block, true]
|
|
|
} else if (isFragment(block)) {
|
|
|
return getInnerBlock(block.nodes)
|
|
|
}
|
|
|
+ return [undefined, false]
|
|
|
}
|
|
|
|
|
|
function isInteropFragment(block: Block): block is VaporFragment {
|
|
|
return !!(isFragment(block) && block.vnode)
|
|
|
}
|
|
|
|
|
|
-function findInteropFragment(block: Block): VaporFragment | undefined {
|
|
|
- if (isInteropFragment(block)) {
|
|
|
- return block
|
|
|
- }
|
|
|
- if (isFragment(block)) {
|
|
|
- return findInteropFragment(block.nodes)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
function getInstanceFromCache(
|
|
|
cached: VaporComponentInstance | VaporFragment,
|
|
|
): GenericComponentInstance {
|