解决使用keep-alive缓存的组件添加key后,热重载失效问题
在使用keep-alive缓存组件时,有些情况下,需要给组件添加key,如下<keep-alive><component v-if="show"key="1"/></keep-alive>这时修改组件component,热重载常常不能成功,组件对应dom消失,页面空白;vue热重载使用的是vue-hot-reload-api,热重载基本分为两种情况rerende
·
在使用keep-alive缓存组件时,有些情况下,需要给组件添加key,如下
<keep-alive>
<component v-if="show" key="1"/>
</keep-alive>
这时修改组件component,热重载常常不能成功,组件对应dom消失,页面空白;
vue热重载使用的是vue-hot-reload-api,热重载基本分为两种情况
- rerender 只有template或者render函改变的情况下使用。
- reload 如果template或者render未改变,则这个函数需要调用reload方法先销毁然后重新创建(包括它的子组件)。
其中reload在重建组件时,组件cid会变化,热重载bug会在此时出现。
keep-alive在设计时完全没考虑热重载的情况,以下是keep-alive判断vnode是否相同的代码
var 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;
如果组件没添加key,发生热重载时,由于cid发生变化,keep-alive会缓存新的vnode,但不会销毁旧的。当组件添加key后,虽然热重载时cid会变化,但kee-alive此时是根据key来判断缓存的,由于key没有变化,keep-alive不会缓存新的vnode,导致缓存失效。
以下是修改过的keep-alive组件,增加了cid判断,并在热重载后销毁了旧的组件。
/**
* 重写keep-alive组件, 解决process.env.NODE_ENV === "development"时热重载bug
*/
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 isAsyncPlaceholder (node) {
return node.isComment && node.asyncFactory
}
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);
}
更多推荐
已为社区贡献2条内容
所有评论(0)