app-keep-alive.vue 6.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
<script>
export const patternTypes = [String, RegExp, Array];
export default {
  name: 'AppKeepAlive',
  props: {
    // 根据组件 name 进行匹配。如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值)。匿名组件不能被匹配。
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number],
    keyList: [Array],
  },
  data() {
    return {
      // eslint-disable-next-line vue/no-reserved-keys
      _toString: Object.prototype.toString,
    };
  },
  watch: {
    include(val) {
      const _this = this;
      _this.pruneCache(function (name) {
        return _this.matches(val, name);
      });
    },
    exclude(val) {
      const _this = this;
      _this.pruneCache(function (name) {
        return !_this.matches(val, name);
      });
    },
    keyList(val) {
      const _this = this;
      // 配置了keyList但是下方插件key
      _this.pruneCache2(function (name) {
        return !_this.matches(val, name);
      });
    },
  },
  created() {
    // 保存缓存的组件
    this.cache = Object.create(null);
    // 保存缓存的组件的key
    this.keys = [];
  },
  destroyed() {
    const _this = this;
    // eslint-disable-next-line no-restricted-syntax, guard-for-in
    for (const key in _this.cache) {
      _this.pruneCacheEntry(_this.cache, key, _this.keys);
    }
  },
  methods: {
    pruneCacheEntry(cache, key, keys, _current) {
      const cached = cache[key];
      if (cached) {
        cached.componentInstance.$destroy();
      }
      // eslint-disable-next-line no-param-reassign
      cache[key] = null;
      this.remove(keys, key);
    },
    pruneCache(filter) {
      const _this = this;
      const cache = _this.cache;
      const keys = _this.keys;
      const _vnode = _this._vnode;
      // eslint-disable-next-line no-restricted-syntax, guard-for-in
      for (const key in cache) {
        const cachedNode = cache[key];
        if (cachedNode) {
          const name = _this.getComponentName(cachedNode.componentOptions);
          if (name && !filter(name)) {
            _this.pruneCacheEntry(cache, key, keys, _vnode);
          }
        }
      }
    },
    pruneCache2(filter) {
      const _this = this;
      const cache = _this.cache;
      const keys = _this.keys;
      const _vnode = _this._vnode;
      // eslint-disable-next-line no-restricted-syntax, guard-for-in
      for (const key in cache) {
        const cachedNode = cache[key];
        if (cachedNode) {
          const name = cachedNode.data.curPath;
          if (name && filter(name)) {
            _this.pruneCacheEntry(cache, key, keys, _vnode);
          }
        }
      }
    },
    matches(pattern, name) {
      if (Array.isArray(pattern)) {
        return pattern.indexOf(name) > -1;
      }
      if (typeof pattern === 'string') {
        return pattern.split(',').indexOf(name) > -1;
      }
      if (this.isRegExp(pattern)) {
        return pattern.test(name);
      }
      /* istanbul ignore next */
      return false;
    },
    getComponentName(opts) {
      return opts && (opts.Ctor.options.name || opts.tag);
    },
    getFirstComponentChild(children) {
      const _this = this;
      if (Array.isArray(children)) {
        for (let i = 0; i < children.length; i++) {
          const c = children[i];
          if (
            _this.isDef(c) &&
            (_this.isDef(c.componentOptions) || _this.isAsyncPlaceholder(c))
          ) {
            return c;
          }
        }
      }
    },
    isAsyncPlaceholder(node) {
      return node.isComment && node.asyncFactory;
    },
    isDef(v) {
      return v !== undefined && v !== null;
    },
    isRegExp(v) {
      return this._toString.call(v) === '[object RegExp]';
    },
    remove(arr, item) {
      if (arr.length) {
        const index = arr.indexOf(item);
        if (index > -1) {
          return arr.splice(index, 1);
        }
      }
    },
  },
  render: function render() {
    // this.$slots.default 包含了所有没有被包含在具名插槽中的节点
    // 这里取得第一个子组件
    const _this = this;
    const slot = _this.$slots.default;
    const vnode = _this.getFirstComponentChild(slot);
    const componentOptions = vnode && vnode.componentOptions;
    if (componentOptions) {
      // check pattern
      const name = _this.getComponentName(componentOptions);
      const ref = _this;
      const include = ref.include;
      const exclude = ref.exclude;
      const keyList = ref.keyList;
      // 获取第一个子组件上面的key
      const slotKey = vnode.key;
      // 如果 componentName 没有作为keep-alive被包含进来,直接返回
      if (
        // 包括并且不匹配的
        (include && (!name || !_this.matches(include, name))) ||
        // 排除并且匹配的
        (exclude && name && _this.matches(exclude, name)) ||
        // keyList中存在并且不匹配的
        (keyList && !slotKey && !_this.matches(keyList, slotKey))
      ) {
        return vnode;
      }

      const ref$1 = _this;
      const cache = ref$1.cache;
      const keys = ref$1.keys;
      const key =
        vnode.key == null
          ? // 相同的构造器(constructor)可能会注册为不同的本地组件,所以仅有一个 cid 是不够的
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : '')
          : vnode.key;
      if (cache[key]) {
        // 如果已经缓存了,需要保持当前的key 是最新的
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        _this.remove(keys, key);
        keys.push(key);
      } else {
        // 否则,进行缓存并更新keys数组。
        cache[key] = vnode;
        keys.push(key);
        // 缓存组件超出最大值,将队首的组件销毁(先进先出)
        if (_this.max && keys.length > parseInt(_this.max, 10)) {
          _this.pruneCacheEntry(cache, keys[0], keys, _this._vnode);
        }
      }

      vnode.data.keepAlive = true;
      vnode.data.curPath = slotKey;
    }
    return vnode || (slot && slot[0]);
  },
};
</script>