feat(KeepAlive): support matchBy + allow custom caching strategy
This is a copy of the old PR https://github.com/vuejs/vue-next/pull/3414, for some reasons, I had to reopen a new one.
RFC vuejs/rfcs#284
Looking forward to this feature.
Looking forward to this feature.
大概在之后哪个版本可以正式用上他呢
Looking forward to this feature.
Looking forward to this feature
ABOUT这个,有没有计划上线啊... BROTHER. [狗头]

Looking forward...
This keep-alive custom purge feature is so important for multi tab pages. And it have been discussed many time since Vue2. I don't why this is not fixed yet now. Vue3 team should really think about it.
As @tony-gm says, this feature is essential for multi-tab pages where the tabs are instances of the same component and the key is being used to differentiate them. For me it is enough that the include/exclude can match on key as well as name, as the custom caching part is what I would consider advanced functionality. So can the key matching be implemented by itself as this is a much simpler (and presumably less risky?) change. Hard to see why it has not already been done when it seems so straightforward.
Here is a temporary solution I can share, hope can help somebody who also work on sort of "multi tab page" project.
- Since keep-alive just a component, so we can get it's instance by set a ref
- I went through keepAlive's source code, found that all the cached vnode keep in a map named __v_cache. Keep-alive internal also just check the
incude,exclude,maxand delete it form the __v_cache, It's not complicate. - So I follow the internal
pruneCacheEntryfunction make aremoveCachefunction to manipulate the __v_cache by myself.
function removeCache(cacheKey) {
const keepAliveComponent = vm.proxy.$refs.keepAlive.$;
const cacheMap = keepAliveComponent.__v_cache;
const cachedVnode = cacheMap.get(cacheKey);
if (cachedVnode) {
const COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8;
const COMPONENT_KEPT_ALIVE = 1 << 9;
let shapeFlag = cachedVnode.shapeFlag;
if (shapeFlag & COMPONENT_SHOULD_KEEP_ALIVE) {
shapeFlag -= COMPONENT_SHOULD_KEEP_ALIVE;
}
if (shapeFlag & COMPONENT_KEPT_ALIVE) {
shapeFlag -= COMPONENT_KEPT_ALIVE;
}
cachedVnode.shapeFlag = shapeFlag;
const keepAliveRenderer = keepAliveComponent.ctx.renderer;
keepAliveRenderer.um(
cachedVnode,
keepAliveComponent,
keepAliveComponent.suspense,
false,
false
);
cacheMap.delete(cacheKey);
}
}
It's work , now we can remove any cache by a key; BUT, in keepAlive source code, the __v_cache only valid for dev mode, not production mode.
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
;(instance as any).__v_cache = cache
}
Base on this, the removeCache function is not suite for production mode. Then I try to follow the official keepAlive to create a myKeepAlive component. Unfortunately keepAlive used a lot of vue internal core function which not exposed for enduser. So I failed and give it up.
FINALLY, there is only one way I can do is to modify the vue distributed file to remove the DEV predication, just like below
let current = null;
//if ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {
instance.__v_cache = cache;
//}
const parentSuspense = instance.suspense;
Now removeCache working again. And I made a build script to automatically replace it, and added it in my build command.
var fs = require("fs");
var path = require("path");
var vue_bundler_file = path.resolve(
__dirname,
"../../node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js"
);
fs.readFile(vue_bundler_file, "utf8", function (err, data) {
if (err) console.error(err);
let orginal_str =
" if ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {\r\n instance.__v_cache = cache;\r\n }";
let target_str =
" //if ((process.env.NODE_ENV !== 'production') || __VUE_PROD_DEVTOOLS__) {\r\n instance.__v_cache = cache;\r\n //}";
var result = data.replace(orginal_str, target_str);
fs.writeFile(vue_bundler_file, result, "utf8", function (err) {
if (err) return console.error(err);
});
});
"scripts": {
"serve": "vue-cli-service serve",
"build": "node ./src/build/replace-vue.js && vue-cli-service build",
"lint": "vue-cli-service lint"
}
``
So far it works for me, I hope it would be help for somebody. If anybody have some other solution. Also please shared it to me, we can discuss it together, thanks.
Thanks @tony-gm, I had already encountered the solution to use the cache directly and also ran into the production mode problem but your subsequent fix for this has solved this for me too. It will keep us going until Vue can add the correct functionality to keep-alive Many thanks for this!
Since the keep-alive keep bother me a long time. I have a proposal
- Provide a composoable function
createKeepAliveCache, return aKeepAliveCacheobject.
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
}
-
KeepAlive component, provide a new prop named
cache, then user can v-bind tocreateKeepAliveCache()returnedKeepAliveCacheobject. -
User can change
include,exclude,maxto maniplate cache as current API (since it's aRef), alsoremove(key)can remove a cache by key,clear()to clear all cache.
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
Hope vuejs team can enhance keep-alive component like this.
we really need the feature, more and more apps with multi tabs pop up
Need this feature.
This feature is the only thing keeping me from migrating to vue 3, looking forward to this feature.
Need this feature !
include 其实就可以实现了,把组件包裹一层自定义一下的 name 就可以了。
参考: https://github.com/hminghe/md-admin-element-plus/blob/main/src/components/multi-window/components/MultiWindowKeepAlive.vue
include actually does that,Wrap the component around a custom name.
see: https://github.com/hminghe/md-admin-element-plus/blob/main/src/components/multi-window/components/MultiWindowKeepAlive.vue
不过还有一点点BUG, 等这个合并了就完美了。https://github.com/vuejs/core/pull/6235
已换react,但是还是想问什么时候可以提供,至少vue2还提供了$destroy,vue3把这个api去掉了,还不提供删除keepalive缓存的方法,也不提供动态修改组件name的方法
应该大部分都是做多tab单页应用的来提这个feature.
@liweijian1 在页面组件外面包个壳,像下面这样子,能完全可控操作keep-alive缓存, 而且不需要额外的内部规范,比如,页面组件必须强制加上组件名,配合在meta里配置上页面组件名称,
<template>
<router-view v-slot="{ Component, route }">
<keep-alive :include="include">
<component :is="wrap(route.fullPath, Component)" :key="route.fullPath" />
</keep-alive>
</router-view>
</tempate>
<script>
import { h } from "vue";
// 自定义name的壳的集合
const wrapperMap = new Map();
export default {
data() {
return {
include: [],
};
},
watch: {
$route: {
handler(next) {
// ??这个按自己业务需要,看是否需要cache页面组件
const index = store.list.findIndex(
(item) => item.fullPath === next.fullPath
);
// 如果没加入这个路由记录,则加入路由历史记录
if (index === -1) {
this.include.push(next.fullPath);
}
},
immediate: true,
},
},
methods: {
// 为keep-alive里的component接收的组件包上一层自定义name的壳.
wrap(fullPath, component) {
let wrapper;
// 重点就是这里,这个组件的名字是完全可控的,
// 只要自己写好逻辑,每次能找到对应的外壳组件就行,完全可以写成任何自己想要的名字.
// 这就能配合 keep-alive 的 include 属性可控地操作缓存.
const wrapperName = fullPath;
if (wrapperMap.has(wrapperName)) {
wrapper = wrapperMap.get(wrapperName);
} else {
wrapper = {
name: wrapperName,
render() {
return h("div", { className: "vaf-page-wrapper" }, component);
},
};
wrapperMap.set(wrapperName, wrapper);
}
return h(wrapper);
},
},
};
</script>
同时,这里包了一个壳,也有另外一个好处。 当外边有transition做过渡时,页面组件即使有多个节点,因为包了这个壳,也能顺利完成过渡效果。
虽然,但是,如果本身提供直接完全可控操作keep-alive缓存的api,显然是更好的。
我这边是想完全自主的管理缓存,不用keep-alive,要使用自己的组件,只是确实这个APi
@hezhongfeng 还是得用keep-alive。现在提供的api也不能让你不用keep-alive就去操作组件缓存。
可以的,Vue2.x 就可以 vue-page-stack
为什么这么好的提议不被支持呢
需要这个特性。
应该大部分都是做多tab单页应用的来提这个feature.
@liweijian1 在页面组件外面包个壳,像下面这样子,能完全可控操作keep-alive缓存, 而且不需要额外的内部规范,比如,页面组件必须强制加上组件名,配合在meta里配置上页面组件名称,
<template> <router-view v-slot="{ Component, route }"> <keep-alive :include="include"> <component :is="wrap(route.fullPath, Component)" :key="route.fullPath" /> </keep-alive> </router-view> </tempate> <script> import { h } from "vue"; // 自定义name的壳的集合 const wrapperMap = new Map(); export default { data() { return { include: [], }; }, watch: { $route: { handler(next) { // ??这个按自己业务需要,看是否需要cache页面组件 const index = store.list.findIndex( (item) => item.fullPath === next.fullPath ); // 如果没加入这个路由记录,则加入路由历史记录 if (index === -1) { this.include.push(next.fullPath); } }, immediate: true, }, }, methods: { // 为keep-alive里的component接收的组件包上一层自定义name的壳. wrap(fullPath, component) { let wrapper; // 重点就是这里,这个组件的名字是完全可控的, // 只要自己写好逻辑,每次能找到对应的外壳组件就行,完全可以写成任何自己想要的名字. // 这就能配合 keep-alive 的 include 属性可控地操作缓存. const wrapperName = fullPath; if (wrapperMap.has(wrapperName)) { wrapper = wrapperMap.get(wrapperName); } else { wrapper = { name: wrapperName, render() { return h("div", { className: "vaf-page-wrapper" }, component); }, }; wrapperMap.set(wrapperName, wrapper); } return h(wrapper); }, }, }; </script>同时,这里包了一个壳,也有另外一个好处。 当外边有transition做过渡时,页面组件即使有多个节点,因为包了这个壳,也能顺利完成过渡效果。
虽然,但是,如果本身提供直接完全可控操作keep-alive缓存的api,显然是更好的。
按照这个包裹实现之后,vue报性能警告

@chenhaihong 请问大佬知道是怎么回事吗?
应该大部分都是做多tab单页应用的来提这个feature. @liweijian1 在页面组件外面包个壳,像下面这样子,能完全可控操作keep-alive缓存, 而且不需要额外的内部规范,比如,页面组件必须强制加上组件名,配合在meta里配置上页面组件名称,
<template> <router-view v-slot="{ Component, route }"> <keep-alive :include="include"> <component :is="wrap(route.fullPath, Component)" :key="route.fullPath" /> </keep-alive> </router-view> </tempate> <script> import { h } from "vue"; // 自定义name的壳的集合 const wrapperMap = new Map(); export default { data() { return { include: [], }; }, watch: { $route: { handler(next) { // ??这个按自己业务需要,看是否需要cache页面组件 const index = store.list.findIndex( (item) => item.fullPath === next.fullPath ); // 如果没加入这个路由记录,则加入路由历史记录 if (index === -1) { this.include.push(next.fullPath); } }, immediate: true, }, }, methods: { // 为keep-alive里的component接收的组件包上一层自定义name的壳. wrap(fullPath, component) { let wrapper; // 重点就是这里,这个组件的名字是完全可控的, // 只要自己写好逻辑,每次能找到对应的外壳组件就行,完全可以写成任何自己想要的名字. // 这就能配合 keep-alive 的 include 属性可控地操作缓存. const wrapperName = fullPath; if (wrapperMap.has(wrapperName)) { wrapper = wrapperMap.get(wrapperName); } else { wrapper = { name: wrapperName, render() { return h("div", { className: "vaf-page-wrapper" }, component); }, }; wrapperMap.set(wrapperName, wrapper); } return h(wrapper); }, }, }; </script>同时,这里包了一个壳,也有另外一个好处。 当外边有transition做过渡时,页面组件即使有多个节点,因为包了这个壳,也能顺利完成过渡效果。 虽然,但是,如果本身提供直接完全可控操作keep-alive缓存的api,显然是更好的。
按照这个包裹实现之后,vue报性能警告
@chenhaihong 请问大佬知道是怎么回事吗?
不要使用reactive包裹你的组件变量, 可以用shallowReactive
应该大部分都是做多tab单页应用的来提这个feature. @liweijian1 在页面组件外面包个壳,像下面这样子,能完全可控操作keep-alive缓存, 而且不需要额外的内部规范,比如,页面组件必须强制加上组件名,配合在meta里配置上页面组件名称,
<template> <router-view v-slot="{ Component, route }"> <keep-alive :include="include"> <component :is="wrap(route.fullPath, Component)" :key="route.fullPath" /> </keep-alive> </router-view> </tempate> <script> import { h } from "vue"; // 自定义name的壳的集合 const wrapperMap = new Map(); export default { data() { return { include: [], }; }, watch: { $route: { handler(next) { // ??这个按自己业务需要,看是否需要cache页面组件 const index = store.list.findIndex( (item) => item.fullPath === next.fullPath ); // 如果没加入这个路由记录,则加入路由历史记录 if (index === -1) { this.include.push(next.fullPath); } }, immediate: true, }, }, methods: { // 为keep-alive里的component接收的组件包上一层自定义name的壳. wrap(fullPath, component) { let wrapper; // 重点就是这里,这个组件的名字是完全可控的, // 只要自己写好逻辑,每次能找到对应的外壳组件就行,完全可以写成任何自己想要的名字. // 这就能配合 keep-alive 的 include 属性可控地操作缓存. const wrapperName = fullPath; if (wrapperMap.has(wrapperName)) { wrapper = wrapperMap.get(wrapperName); } else { wrapper = { name: wrapperName, render() { return h("div", { className: "vaf-page-wrapper" }, component); }, }; wrapperMap.set(wrapperName, wrapper); } return h(wrapper); }, }, }; </script>同时,这里包了一个壳,也有另外一个好处。 当外边有transition做过渡时,页面组件即使有多个节点,因为包了这个壳,也能顺利完成过渡效果。 虽然,但是,如果本身提供直接完全可控操作keep-alive缓存的api,显然是更好的。
按照这个包裹实现之后,vue报性能警告
@chenhaihong 请问大佬知道是怎么回事吗?
不要使用
reactive包裹你的组件变量, 可以用shallowReactive
原因找到了,我用了pinia存储了wrapperMap,所以组件变成了响应式的。
Looking forward to this feature
keep-alive这个特性可以使用了吗?
#7702
This scheme can be tried while waiting for the merger, although it is extremely harmful. But you can use it now