13.3.0
https://codesandbox.io/s/vyy3yokjj0
1.modify the code in the script tag of the Hello component
2.the router-view component disappear from the page
3.we can find the component is activated with devtools
Hot reload normally
Have to refresh the page manually each time modify the code
I came here to report this issue back here, but it's already reported, so I just share the original issue from vue-router then to create a link.
Does anyone have a work around?
Anyone? :)
Still nothing?
This still persists. Is everyone using keep-alive just refreshing each time still?
This still persists. Is everyone using keep-alive just refreshing each time still?
For the most part... sigh
Very annoying.
I just spent a few hours to find where the issue comes from (blank page after HMR).
In my case it seems to happen only if the edit involves a new line character.
@mcoope31
This still persists. Is everyone using keep-alive just refreshing each time still?
I am commenting <keep-alive> while working on a component inside scoped inside it.
Any workaround?
The best workaround I found is
<nuxt :keep-alive="!isDev" />
where isDev = process.env.NODE_ENV === 'development'
haven't thought about it since.
Same problem.
Workaround: disable keep-alive for debug.
Warning: view component must have name
<template>
<keep-alive :exclude="exclude">
<router-view></router-view>
</keep-alive>
</template>
<script>
export default {
computed: {
exclude () {
if (process.env.NODE_ENV === 'production') {
return ''
}
return /.+/
}
}
}
</script>
因为router-view 给了key 所以会白屏
<keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
keep-alive 的源码: 缓存的key 就是取的 router-view 的key;
const { cache, keys } = this
const key: ?string = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
每次hot update 的时候 cid 是会变化的 看 下面 vue-hot-reload 的源码 record.Ctor.cid = newCtor.cid
if (record.Ctor) {
if (version[1] < 2) {
// preserve pre 2.2 behavior for global mixin handling
record.Ctor.extendOptions = options
}
var newCtor = record.Ctor.super.extend(options)
// prevent record.options._Ctor from being overwritten accidentally
newCtor.options._Ctor = record.options._Ctor
record.Ctor.options = newCtor.options
record.Ctor.cid = newCtor.cid
record.Ctor.prototype = newCtor.prototype
if (newCtor.release) {
// temporary global mixin strategy used in < 2.0.0-alpha.6
newCtor.release()
}
} else {
updateOptions(record.options, options)
}
修改方法:keep-alive组件缓存 key的取法 加上 (componentOptions.Ctor as any).cid
const key = vnode.key
? (componentOptions.Ctor as any).cid + (componentOptions.tag ? `::${componentOptions.tag}` : "")
: vnode.key + (componentOptions.Ctor as any).cid;
组件全局覆盖,伪代码:
const cmpt = Vue.component("KeepAlive"); // 混入原生组件,重写render
delete (cmpt as any).mounted;
const newKeepAlive= Vue.extend({
name: "keep-alive",
mixins: [cmpt],
render() {
// override
}
export default function install(vue){
process.env.NODE_ENV === "develop" && vue.component("keep-alive",newKeepAlive)
}
@sodatea 大佬能修复下吗,没搞过pr
Workaround: disable keep-alive for debug.
Warning: view component must have name<template> <keep-alive :exclude="exclude"> <router-view></router-view> </keep-alive> </template> <script> export default { computed: { exclude () { if (process.env.NODE_ENV === 'production') { return '' } return /.+/ } } } </script>
it's working!👍
@nailfar
for vue 2.6, i did some work around, this will require name to be set in component:
```js
import Vue from "vue"
/*
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number],
},
created() {
this.cache = Object.create(null)
this.cachedNameKeyMap = Object.create(null)
this.keys = []
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {
this.$watch("include", val => {
pruneCache(this, name => matches(val, name))
})
this.$watch("exclude", val => {
pruneCache(this, name => !matches(val, name))
})
},
render() {
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, cachedNameKeyMap, keys } = this
const key =
vnode.key == null
? // same constructor may get registered as different local components
// so cid alone is not enough (#3269)
componentOptions.Ctor.cid +
(componentOptions.tag ? ::${componentOptions.tag} : "")
: vnode.key
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// prune old component for hmr
if (name && cachedNameKeyMap[name] && cachedNameKeyMap[name] !== key) {
pruneCacheEntry(cache, cachedNameKeyMap[name], keys)
}
cachedNameKeyMap[name] = key
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
},
}
// ovveride original keep-alive
process.env.NODE_ENV === "development" && Vue.component("KeepAlive", KeepAlive)
For me, this only happens when I'm adding new line / deleting existing line to the template. If I'm modifying existing line, the hot reload works fine.
The newline issue is tracked here: https://github.com/vuejs/vue-loader/issues/1682
@nailfar & @ericwu-wish thank you so much for the direction here. I would have been completely lost without the callout regarding componentOptions.Ctor.cid.
The solution I landed on is pretty much on par with what @nailfar wrote up which I tested and have verified in multiple scenarios.
To make this easier I've created a plugin which will resolve the issue for anyone interested: https://www.npmjs.com/package/vue-keep-alive-dev
Though, honestly, I wonder if there's really much harm in appending the cid to the cache globally as it stays stationary in line with the component even when HMR isn't running (though I may be missing something) so I'm going to throw out a PR to Vue for consideration.
I refreshing each time.Very annoying.
Hope it can be fixed soon.
@sodatea hao dalao,when fix it?
its so unfriendly
Update:
I hear this is a non issue on the more granular Webpack 5... so I'll just focus on upgrading to that.
Though if I need this now, I'll look on getting rid of the dynamic ID on the router-view and use props, vuex and better state management instead.
I need a better workaround...
I really can't disable keep-alive as it's critical to letting the browser know I've interacted with the page. And the runtime behavior of the app is completely different without keep alive.
3 year old issue. Any new workarounds?
Any reason the maintainers aren't looking at this? It's not a minor issue.
Any update ?
Add cid comparison.
````
import Vue from "vue";
let patternTypes = [String, RegExp, Array];
function pruneCacheEntry(cache, key, keys, current) {
let cached$$1 = cache[key];
if (cached$$1 && (!current || cached$$1.tag !== current.tag)) {
cached$$1.componentInstance.$destroy();
}
cache[key] = null;
remove(keys, key);
}
function pruneCache(keepAliveInstance, filter) {
let cache = keepAliveInstance.cache;
let keys = keepAliveInstance.keys;
let _vnode = keepAliveInstance._vnode;
for (let key in cache) {
let cachedNode = cache[key];
if (cachedNode) {
let name = getComponentName(cachedNode.componentOptions);
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode);
}
}
}
}
function matches(pattern, name) {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
/* istanbul ignore next */
return false
}
function isDef(v) {
return v !== undefined && v !== null
}
/**
function getFirstComponentChild(children) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
let c = children[i];
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
function getComponentName(opts) {
return opts && (opts.Ctor.options.name || opts.tag)
}
const keepAlive = {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created: function created() {
this.cache = Object.create(null);
this.keys = [];
this.$emit('getInstance', this);
},
destroyed: function destroyed() {
for (let key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys);
}
},
mounted: function mounted() {
let this$1 = this;
this.$watch('include', function (val) {
pruneCache(this$1, function (name) {
return matches(val, name);
});
});
this.$watch('exclude', function (val) {
pruneCache(this$1, function (name) {
return !matches(val, name);
});
});
},
render: function render() {
let slot = this.$slots.default;
let vnode = getFirstComponentChild(slot);
let componentOptions = vnode && vnode.componentOptions;
if (componentOptions) {
if (componentOptions.Ctor) {
vnode._cid = componentOptions.Ctor.cid;//记录cid
}
// check pattern
let name = getComponentName(componentOptions);
let ref = this;
let include = ref.include;
let exclude = ref.exclude;
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
let ref$1 = this;
let cache = ref$1.cache;
let keys = ref$1.keys;
let key = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '')
: vnode.key;
if (cache[key]) {
//判断cid是否相同, 不同则有过热重载的reload, 需要重建缓存
if (vnode._cid === cache[key]._cid) {
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
remove(keys, key);
keys.push(key);
} else {
cache[key].componentInstance.$destroy();
cache[key] = vnode;
}
} else {
cache[key] = vnode;
keys.push(key);
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode);
}
}
vnode.data.keepAlive = true;
}
return vnode || (slot && slot[0])
}
};
//只在开发模式下生效
if (process.env.NODE_ENV === "development") {
Vue.component('keep-alive', keepAlive);
}
````
Add cid comparison.
import Vue from "vue"; let patternTypes = [String, RegExp, Array]; function pruneCacheEntry(cache, key, keys, current) { let cached$$1 = cache[key]; if (cached$$1 && (!current || cached$$1.tag !== current.tag)) { cached$$1.componentInstance.$destroy(); } cache[key] = null; remove(keys, key); } function pruneCache(keepAliveInstance, filter) { let cache = keepAliveInstance.cache; let keys = keepAliveInstance.keys; let _vnode = keepAliveInstance._vnode; for (let key in cache) { let cachedNode = cache[key]; if (cachedNode) { let name = getComponentName(cachedNode.componentOptions); if (name && !filter(name)) { pruneCacheEntry(cache, key, keys, _vnode); } } } } function matches(pattern, name) { if (Array.isArray(pattern)) { return pattern.indexOf(name) > -1 } else if (typeof pattern === 'string') { return pattern.split(',').indexOf(name) > -1 } else if (isRegExp(pattern)) { return pattern.test(name) } /* istanbul ignore next */ return false } function isDef(v) { return v !== undefined && v !== null } /** * Remove an item from an array. */ function remove(arr, item) { if (arr.length) { var index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1) } } } function getFirstComponentChild(children) { if (Array.isArray(children)) { for (let i = 0; i < children.length; i++) { let c = children[i]; if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { return c } } } } function getComponentName(opts) { return opts && (opts.Ctor.options.name || opts.tag) } const keepAlive = { name: 'keep-alive', abstract: true, props: { include: patternTypes, exclude: patternTypes, max: [String, Number] }, created: function created() { this.cache = Object.create(null); this.keys = []; this.$emit('getInstance', this); }, destroyed: function destroyed() { for (let key in this.cache) { pruneCacheEntry(this.cache, key, this.keys); } }, mounted: function mounted() { let this$1 = this; this.$watch('include', function (val) { pruneCache(this$1, function (name) { return matches(val, name); }); }); this.$watch('exclude', function (val) { pruneCache(this$1, function (name) { return !matches(val, name); }); }); }, render: function render() { let slot = this.$slots.default; let vnode = getFirstComponentChild(slot); let componentOptions = vnode && vnode.componentOptions; if (componentOptions) { if (componentOptions.Ctor) { vnode._cid = componentOptions.Ctor.cid;//记录cid } // check pattern let name = getComponentName(componentOptions); let ref = this; let include = ref.include; let exclude = ref.exclude; if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode } let ref$1 = this; let cache = ref$1.cache; let keys = ref$1.keys; let key = vnode.key == null // same constructor may get registered as different local components // so cid alone is not enough (#3269) ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '') : vnode.key; if (cache[key]) { //判断cid是否相同, 不同则有过热重载的reload, 需要重建缓存 if (vnode._cid === cache[key]._cid) { vnode.componentInstance = cache[key].componentInstance; // make current key freshest remove(keys, key); keys.push(key); } else { cache[key].componentInstance.$destroy(); cache[key] = vnode; } } else { cache[key] = vnode; keys.push(key); // prune oldest entry if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } } vnode.data.keepAlive = true; } return vnode || (slot && slot[0]) } }; //只在开发模式下生效 if (process.env.NODE_ENV === "development") { Vue.component('keep-alive', keepAlive); }
最好不要粘贴大段的代码,尽量把代码分段解释。
Most helpful comment
Workaround: disable keep-alive for debug.
Warning: view component must have name