import { VNode, nextTick, computed, ConcreteComponent, ComponentPublicInstance, } from 'vue' import { useRoute, RouteLocationNormalizedLoaded } from 'vue-router' import { removeLeadingSlash } from '@dcloudio/uni-shared' import { usePageMeta } from './provide' const SEP = '$$' const currentPagesMap = new Map() function pruneCurrentPages() { currentPagesMap.forEach((page, id) => { if (((page as unknown) as ComponentPublicInstance).$.isUnmounted) { currentPagesMap.delete(id) } }) } export function getCurrentPages(isAll: boolean = false) { pruneCurrentPages() // TODO 目前页面unmounted时机较晚,前一个页面onShow里边调用getCurrentPages,可能还会获取到上一个准备被销毁的页面 return [...currentPagesMap.values()] } export function removeCurrentPages(delta: number = -1) { const keys = [...currentPagesMap.keys()] const start = keys.length - 1 const end = start - delta for (let i = start; i > end; i--) { const routeKey = keys[i] const pageVm = currentPagesMap.get(routeKey) as ComponentPublicInstance pageVm.$.__isUnload = true pageVm.$callHook('onUnload') currentPagesMap.delete(routeKey) } } let id = (history.state && history.state.__id__) || 1 export function createPageState( type: 'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' ) { return { __id__: ++id, __type__: type, } } export function isPage(vm: ComponentPublicInstance) { // @dcloudio/vite-plugin-uni/src/configResolved/plugins/pageVue.ts return vm.$options.mpType === 'page' } function initPublicPage(route: RouteLocationNormalizedLoaded) { if (!route) { const { path } = __uniRoutes[0] return { id, path, route: path.substr(1), fullPath: path } } const { path } = route return { id, path: path, route: removeLeadingSlash(path), fullPath: route.meta.isEntry ? route.meta.pagePath : route.fullPath, options: {}, // $route.query meta: usePageMeta(), } } export function initPage(vm: ComponentPublicInstance) { const route = vm.$route const page = initPublicPage(route) ;(vm as any).$vm = vm ;(vm as any).$page = page currentPagesMap.set( normalizeRouteKey(page.path, page.id), (vm as unknown) as Page.PageInstance ) } function normalizeRouteKey(path: string, id: number) { return path + SEP + id } export function useKeepAliveRoute() { const route = useRoute() const routeKey = computed(() => normalizeRouteKey(route.path, history.state.__id__ || 1) ) return { routeKey, routeCache, } } // https://github.com/vuejs/rfcs/pull/284 // https://github.com/vuejs/vue-next/pull/3414 type CacheKey = string | number | ConcreteComponent interface KeepAliveCache { get(key: CacheKey): VNode | void set(key: CacheKey, value: VNode): void delete(key: CacheKey): void forEach( fn: (value: VNode, key: CacheKey, map: Map) => void, thisArg?: any ): void pruneCacheEntry?: (cached: VNode) => void } const pageCacheMap = new Map() const routeCache: KeepAliveCache = { get(key) { return pageCacheMap.get(key) }, set(key, value) { pruneRouteCache(key as string) pageCacheMap.set(key, value) }, delete(key) { const vnode = pageCacheMap.get(key) if (!vnode) { return } pageCacheMap.delete(key) }, forEach(fn) { pageCacheMap.forEach(fn) }, } function pruneRouteCache(key: string) { const pageId = parseInt(key.split(SEP)[1]) if (!pageId) { return } routeCache.forEach((vnode, key) => { const cPageId = parseInt((key as string).split(SEP)[1]) if (cPageId && cPageId > pageId) { routeCache.delete(key) routeCache.pruneCacheEntry!(vnode) nextTick(() => pruneCurrentPages()) } }) }