import { ConcreteComponent , getCurrentInstance , SetupContext , ComponentInternalInstance , LifecycleHooks , currentInstance , getComponentName , ComponentOptions } from '../component' ; import { VNode , cloneVNode , isVNode , VNodeProps } from '../vnode' ; import { warn } from '../warning' ; import { onBeforeUnmount , injectHook , onUnmounted , onMounted , onUpdated } from '../apiLifecycle' ; import { isString , isArray , ShapeFlags , remove , invokeArrayFns } from '@vue/shared' ; import { watch } from '../apiWatch' ; import { RendererInternals , queuePostRenderEffect , MoveType , RendererElement , RendererNode , invokeVNodeHook } from '../renderer' ; import { setTransitionHooks } from './BaseTransition' ; import { ComponentRenderContext } from '../componentPublicInstance' ; import { devtoolsComponentAdded } from '../devtools' ; import { isAsyncWrapper } from '../apiAsyncComponent' ; type MatchPattern = string | RegExp | string [ ] | RegExp [ ] ; export interface KeepAliveProps { include ? : MatchPattern ; exclude ? : MatchPattern ; max ? : number | string ; } type CacheKey = string | number | symbol | ConcreteComponent ; type Cache = Map < CacheKey , VNode > ; type Keys = Set < CacheKey > ; export interface KeepAliveContext extends ComponentRenderContext { renderer : RendererInternals ; activate : ( vnode : VNode , container : RendererElement , anchor : RendererNode | null , isSVG : boolean , optimized : boolean ) => void ; deactivate : ( vnode : VNode ) => void ; } export const isKeepAlive = ( vnode ) : boolean => ( vnode . type as any ) . __isKeepAlive ; const KeepAliveImpl = { name : `KeepAlive` , __isKeepAlive : true , props : { include : [ String , RegExp , Array ] , exclude : [ String , RegExp , Array ] , max : [ String , Number ] } , setup ( props , { slots } ) { const instance = getCurrentInstance ( ) ! ; const sharedContext = instance . ctx as KeepAliveContext ; if ( ! sharedContext . renderer ) { return slots . default ; } const cache = new Map ( ) ; const keys = new Set ( ) ; let current = null ; if ( __DEV__ || __FEATURE_PROD_DEVTOOLS__ ) { ; ( instance as any ) . __v_cache = cache ; } const parentSuspense = instance . suspense ; const { renderer : { p : patch , m : move , um : _unmount , o : { createElement } } } = sharedContext ; const storageContainer = createElement ( 'div' ) ; sharedContext . activate = ( vnode , container , anchor , isSVG , optimized ) => { const instance = vnode . component ! ; move ( vnode , container , anchor , MoveType . ENTER , parentSuspense ) ; patch ( instance . vnode , vnode , container , anchor , instance , parentSuspense , isSVG , vnode . slotScopeIds , optimized ) ; queuePostRenderEffect ( ( ) => { instance . isDeactivated = false ; if ( instance . a ) { invokeArrayFns ( instance . a ) ; } const vnodeHook = vnode . props && vnode . props . onVnodeMounted ; if ( vnodeHook ) { invokeVNodeHook ( vnodeHook , instance . parent , vnode ) ; } } , parentSuspense ) ; if ( __DEV__ || __FEATURE_PROD_DEVTOOLS__ ) { devtoolsComponentAdded ( instance ) ; } } ; sharedContext . deactivate = ( vnode ) => { const instance = vnode . component ! ; move ( vnode , storageContainer , null , MoveType . LEAVE , parentSuspense ) ; queuePostRenderEffect ( ( ) => { if ( instance . da ) { invokeArrayFns ( instance . da ) ; } const vnodeHook = vnode . props && vnode . props . onVnodeUnmounted ; if ( vnodeHook ) { invokeVNodeHook ( vnodeHook , instance . parent , vnode ) ; } instance . isDeactivated = true ; } , parentSuspense ) ; if ( __DEV__ || __FEATURE_PROD_DEVTOOLS__ ) { devtoolsComponentAdded ( instance ) ; } } ; function unmount ( vnode ) { resetShapeFlag ( vnode ) ; _unmount ( vnode , instance , parentSuspense ) ; } function pruneCache ( filter ? ) { cache . forEach ( ( vnode , key ) => { const name = getComponentName ( vnode . type as ConcreteComponent ) ; if ( name && ( ! filter || ! filter ( name ) ) ) { pruneCacheEntry ( key ) ; } } ) ; } function pruneCacheEntry ( key ) { const cached = cache . get ( key ) as VNode ; if ( ! current || cached . type !== current . type ) { unmount ( cached ) ; } else if ( current ) { resetShapeFlag ( current ) ; } cache . delete ( key ) ; keys . delete ( key ) ; } watch ( ( ) => [ props . include , props . exclude ] , ( [ include , exclude ] ) => { include && pruneCache ( name => matches ( include , name ) ) ; exclude && pruneCache ( name => ! matches ( exclude , name ) ) ; } , { flush : 'post' , deep : true } ) ; let pendingCacheKey = null ; const cacheSubtree = ( ) => { if ( pendingCacheKey != null ) { cache . set ( pendingCacheKey , getInnerChild ( instance . subTree ) ) ; } } ; onMounted ( cacheSubtree ) ; onUpdated ( cacheSubtree ) ; onBeforeUnmount ( ( ) => { cache . forEach ( cached => { const { subTree , suspense } = instance ; const vnode = getInnerChild ( subTree ) ; if ( cached . type === vnode . type ) { resetShapeFlag ( vnode ) ; const da = vnode . component ! . da ; da && queuePostRenderEffect ( da , suspense ) ; return ; } unmount ( cached ) ; } ) ; } ) ; return ( ) => { pendingCacheKey = null ; if ( ! slots . default ) { return null ; } const children = slots . default ( ) ; const rawVNode = children [ 0 ] ; if ( children . length > 1 ) { if ( __DEV__ ) { warn ( `KeepAlive should contain exactly one component child.` ) ; } current = null ; return children ; } else if ( ! isVNode ( rawVNode ) || ( ! ( rawVNode . shapeFlag & ShapeFlags . STATEFUL_COMPONENT ) && ! ( rawVNode . shapeFlag & ShapeFlags . SUSPENSE ) ) ) { current = null ; return rawVNode ; } let vnode = getInnerChild ( rawVNode ) ; const comp = vnode . type as ConcreteComponent ; const name = getComponentName ( isAsyncWrapper ( vnode ) ? ( vnode . type as ComponentOptions ) . __asyncResolved || { } : comp ) ; const { include , exclude , max } = props ; if ( ( include && ( ! name || ! matches ( include , name ) ) ) || ( exclude && name && matches ( exclude , name ) ) ) { current = vnode ; return rawVNode ; } const key = vnode . key == null ? comp : vnode . key ; const cachedVNode = cache . get ( key ) ; if ( vnode . el ) { vnode = cloneVNode ( vnode ) ; if ( rawVNode . shapeFlag & ShapeFlags . SUSPENSE ) { rawVNode . ssContent = vnode ; } } pendingCacheKey = key ; if ( cachedVNode ) { vnode . el = cachedVNode . el ; vnode . component = cachedVNode . component ; if ( vnode . transition ) { setTransitionHooks ( vnode , vnode . transition ! ) ; } vnode . shapeFlag |= ShapeFlags . COMPONENT_KEPT_ALIVE ; keys . delete ( key ) ; keys . add ( key ) ; } else { keys . add ( key ) ; if ( max && keys . size > parseInt ( max as string , 10 ) ) { pruneCacheEntry ( keys . values ( ) . next ( ) . value ) ; } } vnode . shapeFlag |= ShapeFlags . COMPONENT_SHOULD_KEEP_ALIVE ; current = vnode ; return rawVNode ; } ; } } ; if ( __COMPAT__ ) { KeepAliveImpl . __isBuildIn = true ; } export const KeepAlive = KeepAliveImpl as any as { __isKeepAlive : true ; new ( ) : { $props : VNodeProps & KeepAliveProps ; } ; } ; function matches ( pattern , name ) { if ( isArray ( pattern ) ) { return pattern . some ( ( p ) => matches ( p , name ) ) ; } else if ( isString ( pattern ) ) { return pattern . split ( ',' ) . indexOf ( name ) > - 1 ; } else if ( pattern . test ) { return pattern . test ( name ) ; } return false ; } export function onActivated ( hook , target ? ) { registerKeepAliveHook ( hook , LifecycleHooks . ACTIVATED , target ) ; } export function onDeactivated ( hook , target ? ) { registerKeepAliveHook ( hook , LifecycleHooks . DEACTIVATED , target ) ; } function registerKeepAliveHook ( hook , type , target = currentInstance ) { const wrappedHook = hook . __wdc || ( hook . __wdc = ( ) => { let current = target ; while ( current ) { if ( current . isDeactivated ) { return ; } current = current . parent ; } hook ( ) ; } ) ; injectHook ( type , wrappedHook , target ) ; if ( target ) { let current = target . parent ; while ( current && current . parent ) { if ( isKeepAlive ( current . parent . vnode ) ) { injectToKeepAliveRoot ( wrappedHook , type , target , current ) ; } current = current . parent ; } } } function injectToKeepAliveRoot ( hook , type , target , keepAliveRoot ) { const injected = injectHook ( type , hook , keepAliveRoot , true ) ; onUnmounted ( ( ) => { remove ( keepAliveRoot [ type ] ! , injected ) ; } , target ) ; } function resetShapeFlag ( vnode ) { let shapeFlag = vnode . shapeFlag ; if ( shapeFlag & ShapeFlags . COMPONENT_SHOULD_KEEP_ALIVE ) { shapeFlag -= ShapeFlags . COMPONENT_SHOULD_KEEP_ALIVE ; } if ( shapeFlag & ShapeFlags . COMPONENT_KEPT_ALIVE ) { shapeFlag -= ShapeFlags . COMPONENT_KEPT_ALIVE ; } vnode . shapeFlag = shapeFlag ; } function getInnerChild ( vnode ) { return vnode . shapeFlag & ShapeFlags . SUSPENSE ? vnode . ssContent ! : vnode ; }