在使用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);
}

Logo

前往低代码交流专区

更多推荐