Custom cache strategy and matching rules for KeepAlive #283
Replies: 18 comments 6 replies
-
|
We really need these features. |
Beta Was this translation helpful? Give feedback.
-
|
Hope to merge soon. |
Beta Was this translation helpful? Give feedback.
-
|
This is nice. I'm only worried about the Wouldn't it be possible to only have the Another solution would be calling a method on the What was the point of adding |
Beta Was this translation helpful? Give feedback.
-
|
In vue2.x I can custom a component to replace KeepAlive.But in vue3.x its impossible. |
Beta Was this translation helpful? Give feedback.
-
|
I realy realy need it, otherwise I would need to change a lot of project code 😢 |
Beta Was this translation helpful? Give feedback.
-
|
现在可以用了吗 |
Beta Was this translation helpful? Give feedback.
-
|
Hello, I have to say in advance that I don't quite understand the proposed solution fully. It looks too complex for the task that I/other people want to accomplish based on what I have read (vuejs/core#2077, https://stackoverflow.com/questions/70113769/manually-remove-and-destroy-a-component-from-keep-alive, https://stackoverflow.com/questions/65163775/how-to-destroy-unmount-vue-js-3-components, https://stackoverflow.com/questions/68995879/vue-3-force-unmount-when-deactivated) to name a few. The current situation is - most of the solutions ether involve using For example, if we have cached instances of the same <router-view v-slot="{ Component }">
<keep-alive :include="Item,SomethingElse">
<component :is="Component" :key="$route.fullPath" />
</keep-alive>
</router-view>Given that we visited the page 3 times under different IDs:
Would the proposed solution allow to destroy/unmount a specific instance upon an event in app? Let's say user removed the item from the database so we want the cache of Overall it would be good to know when the solution will be implemented (or the latest status of it), given that after 2 months it's going to be a year since this topic was created. Edit (19.01.2022)In addition to above, I would also like to empathize on required (personally required/subjective) caching customization granularity. If I understand correctly - So let's say we have 3 components we want to cache
If we set (currently) maximum to 1 then every single component is allowed to have 1 cached instance. Is this correct? In comparison, in real world scenario, for the |
Beta Was this translation helpful? Give feedback.
-
|
I have another solution, it's mini change for current API and easy to implement.
export interface KeepAliveCache {
include: Ref<MatchPattern | undefined>
exclude: Ref<MatchPattern | undefined>
max: Ref<number | string | undefined>
cache: Map<string, VNode>
remove: (key: string) => void
clear: () => void
}
Here is a full example <template>
<button @click="onCloseTab">Close Current Tab</button>
<button @click="onCloseAllTabs">Close All Tabs</button>
<router-view v-slot="{ Component, route }">
<keep-alive :cache="pageCache">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
</router-view>
</template>
<script lang="ts">
import { defineComponent, createKeepAliveCache, ref } from 'vue'
export default defineComponent({
name: 'App',
setup() {
const currentPath = ref('')
const pageCache = createKeepAliveCache()
pageCache.include.value = ['Component1']
function onCloseTab() {
pageCache.remove(currentPath.value)
}
function onCloseAllTabs() {
pageCache.clear()
}
return {
pageCache,
onCloseTab,
onCloseAllTabs
}
}
})
</script>I document this in a RFC , more detail please check RFC Link And I also did a prototype implement base on this RFC, currently it's work. Implement Link |
Beta Was this translation helpful? Give feedback.
-
|
Any plan about this keep-alive RFC? |
Beta Was this translation helpful? Give feedback.
-
|
呜呜 请官方下场看看,很久了 |
Beta Was this translation helpful? Give feedback.
-
|
Adding my vote for this feature |
Beta Was this translation helpful? Give feedback.
-
|
really need |
Beta Was this translation helpful? Give feedback.
-
|
I'm waiting this too for managing component states easier, I hope we'll see this feature coming this year. <3 |
Beta Was this translation helpful? Give feedback.
-
|
To anybody still interested in this, I found a way of making it work, although it's still not super great. I'm on Vue 2.7.10 and vue-router 3.6.5.
The parent component within the top level router-link contains this:
The trick is in whatever component you're rendering as a child with multiple versions, for example Add a All of this needs to happen inside whatever is being displayed in the router-link. There definitely needs to be a better API for managing cached state, but at the very least this avoids the most common pitfalls of having multiple child components clog up memory and ignore whatever |
Beta Was this translation helpful? Give feedback.
-
|
I agree with @pavlexander. Many of the RFC's referenced issues (and what brought me here today) are tied to the simple fact that Vue 3 has no way to remove a single component from keep-alive's cache as we could indirectly with Vue 2's component $destroy() method. While I appreciate the RFC author is trying to address multiple cache issues including this missing Vue 3 cache capability, the proposed RFC change is substantial and we are not seeing any incremental cache improvements because they are all tied to one big overhaul change to caching. I think we should try to break down caching changes; as a start, at least into two parts (1) address control of removing a single item and (2) fine-grain cache control (e.g. implementation, hooks). In my opinion, #1 is a core capability of KeepAlive or any caching system. As a suggestion on how to implement #1, we could employ the provide/inject mechanism of Vue 3 here. (Vue 2 is reaching EOL soon and is out-of-scope at this point.) KeepAlive component can provide some cache-service object and any enclosing component can inject this object. As a start, the cache-service object can provide one simple method that the component can use to remove items including itself; e.g. |
Beta Was this translation helpful? Give feedback.
-
|
Are there any plans that this will go forward here? It's a little bit frustrating that you can not clean up the cache when you are using KeepAlive in more advanced (e.g. related to the router view). Sometimes people are using permissions or closing something then you definitely want to remove this cached entry from KeepAlive. |
Beta Was this translation helpful? Give feedback.
-
|
Caching cleanup for anonymous components is a very important feature for the entire Vue. |
Beta Was this translation helpful? Give feedback.
-
|
用KeepAlive的exclude属性控制cache的手动删除倒是可以实现精准控制cache,属于是曲线救国了。 function getComponentName(Component, includeInferred = true) {
return isFunction(Component) ? Component.displayName || Component.name : Component.name || includeInferred && Component.__name;
}此时问题就演变成了如何使每次KeeyAlive【新】渲染的组件有唯一的ID,并这个ID作为Component的name。 以下是我的代码,其中主要涉及router.ts和app.vue两处改动,代码中夹杂了一些判断软返回还是硬返回、控制过场动画等等功能,不影响keepalive部分的逻辑,仅供参考。 文件:router_transition_enhanced.ts import useSerial from '@/views/app/com.student414.digitaloffice/workflow/useSerial'
import type {
NavigationGuardNext,
RouteLocationNormalizedGeneric as _RouteLocationNormalizedGeneric,
Router,
} from 'vue-router'
type RouteLocationNormalizedGeneric = _RouteLocationNormalizedGeneric & {
version: string
}
const SessionStorage_key_Router_Enhanced_History = 'RTE_History' // Router Transition Enhanced History
const idSerial = useSerial(0, 1)
const debug = !true
type RouteRecord = Partial<RouteLocationNormalizedGeneric> & { id: number }
type TransitionEnhancedRouter = Router & {
__proto__: any
onTransitionEnd: () => void
getTransitionName: () => string
pruneCallback: (from: RouteLocationNormalizedGeneric) => void
}
function transitionEnhance(origin: Router): TransitionEnhancedRouter {
// create a new instance from origin
const router = Object.create(origin) as TransitionEnhancedRouter
const routeNokeepCountMap = new Map<string, number>()
let transitionName: string = ''
let history: RouteRecord[] = []
let isInit = false
let offset = 100
let softNavButtonUsed = 0 // in millisecond
const removeLastRouterPath = function (n = 1) {
if (n > 0) {
for (let i = 0; i < n; i++) {
history.pop()
}
sessionStorage.setItem(
SessionStorage_key_Router_Enhanced_History,
JSON.stringify(history),
)
}
}
router.push = function (): any {
offset = 1
softNavButtonUsed = new Date().getTime()
router.__proto__.push.call(this, ...arguments)
}
router.replace = function (): any {
offset = 0
softNavButtonUsed = new Date().getTime()
router.__proto__.replace.call(this, ...arguments)
}
router.go = function (n: number) {
if (n > 0) {
console.error('router.go not supported now')
return
}
offset = n
softNavButtonUsed = new Date().getTime()
router.__proto__.go.call(this, n)
}
router.back = function () {
offset = -1
softNavButtonUsed = new Date().getTime()
router.__proto__.back.call(this, ...arguments)
}
router.forward = function () {
console.error('router.forward not supported now')
return
}
// core route guard
router.beforeEach(((
to: RouteLocationNormalizedGeneric,
from: RouteLocationNormalizedGeneric,
next: NavigationGuardNext,
) => {
if (!isInit) {
// initRouterPaths()
isInit = true
}
if (softNavButtonUsed > 0) {
softNavButtonUsed = 0
// use soft nav button
if (offset === 1) {
const routeRecord = {
...to,
id: idSerial(),
}
history.push(routeRecord)
transitionName = 'slide_left'
to.version = calcRouterVersion(to)
} else if (offset < 0) {
removeLastRouterPath(-offset)
transitionName = 'slide_right'
router.pruneCallback?.(from)
} else if (offset === 0) {
transitionName = 'slide_replace'
router.pruneCallback?.(from)
} else {
console.warn('unknown offset', offset)
}
} else {
// use system nav button
debug && console.log('use system nav button', to, history)
if (
history[history.length - 2] &&
to.fullPath === history[history.length - 2]!.fullPath
) {
removeLastRouterPath(1)
transitionName = 'slide_right'
router.pruneCallback?.(from)
} else if (
history[history.length - 1] &&
to.fullPath === history[history.length - 1]!.fullPath
) {
transitionName = 'slide_replace'
router.pruneCallback?.(from)
} else {
const routeRecord = {
...to,
id: idSerial(),
}
history.push(routeRecord)
transitionName = 'slide_left'
to.version = calcRouterVersion(to)
}
}
next()
}) as (
to: _RouteLocationNormalizedGeneric,
from: _RouteLocationNormalizedGeneric,
next: NavigationGuardNext,
) => {})
// provide a interface to <Transition>, clear transitionName after transition
router.onTransitionEnd = () => {
transitionName = ''
}
const calcRouterVersion = (route: RouteLocationNormalizedGeneric) => {
if (!isInit) {
// initRouterPaths()
isInit = true
}
if (history.length === 0) {
const routeRecord = {
...route,
id: idSerial(),
}
history.push(routeRecord)
transitionName = 'slide_replace'
}
let lastHistory = history[history.length - 1]!
let id: string = ''
if (lastHistory.path !== route.path && lastHistory.name !== route.name) {
console.warn(
'current route name not match!',
'route:',
route,
'history(last):',
history[history.length - 1],
)
}
id = `${(lastHistory.name as string) || lastHistory.path}$${lastHistory.id}`
// when page is not keep-alive, the id should be unique when render
if (!route.meta.keepAlive) {
let keepAliveVersion = routeNokeepCountMap.get(lastHistory.path!) || 0
id = `${id}#${keepAliveVersion}`
routeNokeepCountMap.set(lastHistory.path!, keepAliveVersion + 1)
}
debug && console.log('calcRouterVersion return', id, 'history', history)
return id
}
router.getTransitionName = () => {
return transitionName
}
return router
}
export default transitionEnhance文件:App.vue <template>
<van-config-provider :theme="settingStore.setting.theme" style="width: 100%; height: 100%">
<RouterView v-slot="{ Component, route }">
{{ injectComponentName(Component, route) }}
<template v-if="Component">
<Transition
:name="router.getTransitionName()"
@leave="router.onTransitionEnd"
appear
>
<KeepAlive ref="keepalive" :exclude="data.exclude">
<Suspense timeout="100">
<template #default>
<component
:is="Component"
:key="Component.key || (route as any).version"
></component>
</template>
<!-- 加载中状态 -->
<template #fallback>
<div
style="
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 36vh;
"
>
<div class="loading">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
精彩内容马上呈现...
</div>
</template>
</Suspense>
</KeepAlive>
</Transition>
</template>
</RouterView>
</van-config-provider>
</template>
<script setup lang="ts">
import {
showConfirmDialog,
showFailToast,
showToast,
type FloatingBubbleOffset,
type PopoverAction,
type PopoverPlacement,
} from 'vant'
import { useRect } from '@vant/use'
import messageHub from './message_hub'
import router from '@/router'
import {
reactive,
onMounted,
useTemplateRef,
type VNode,
type RendererNode,
type RendererElement,
nextTick,
} from 'vue'
import axiosInstance from './axios'
import useSettingStore from './stores/setting'
import PlayerIcon from './views/app/com.student414.music/_components/PlayerIcon.vue'
import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'
// import Live2d from '@/components/Live2d.vue'
const keepalive = useTemplateRef('keepalive')
const data = reactive({
exclude: [] as string[],
})
const injectComponentName = (
c: VNode<
RendererNode,
RendererElement,
{
[key: string]: any
}
>,
r: RouteLocationNormalizedLoadedGeneric,
) => {
// ensure component is valid, and c is a 'Component'
if (!c) return
if (!c.type || typeof c.type !== 'object' || !('__name' in c.type)) {
return
}
// when page navigate from next page to previous, c.key would be earsed unexpectedly,
// so we must re-fill it using c.type.__name to prevent cache missing.
// cache missing: prev page's onMounted would be called again, but we just need once.
if (!c.key) {
c.key = c.type.__name as string
}
// ensure we have received a enhanced route, see 'src/route/router_transition_enhanced.ts'
if (!('version' in r)) return
c.key = r.version as string
c.type.__name = r.version as string
}
onMounted(() => {
// setup router.pruneCallback
router.pruneCallback = (from) => {
if (!from.version) return
data.exclude.push(from.version)
// remove heading elements if data.exclude is too large
if (data.exclude.length > 10) {
data.exclude.splice(0, data.exclude.length - 10)
}
}
})
</script>
<style scoped>
</style>最后在原来的router.ts中提升一下router功能即可。 const router = transitionEnhance(
createRouter({
history: createWebHashHistory(import.meta.env.BASE_URL),
routes: r,
}),
)能用,但很奇葩。 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Discussion Thread for "Custom cache strategy and matching rules for KeepAlive"
Links
Beta Was this translation helpful? Give feedback.
All reactions