keep-alive

keep-alive 是 Vue 的一个内置抽象组件,通常用于缓存动态组件或路由组件。

被 keep-alive 包裹的组件在切换时不会被销毁,而是会被缓存下来,下一次切换回这个组件时,会直接复用之前的实例,保持其状态。

keep-alive的几个配置属性和钩子:

1.include 和 exclude: 用于控制哪些组件需要缓存,支持字符串、正则表达式或数组。

2.max: 用于指定缓存的组件数量,当超出这个数量时,最久未使用的组件实例将被销毁。

3.activated:当组件被激活时触发(即从缓存中恢复时)。

4.deactivated:当组件被停用时触发(即被缓存时)。

 

keep-alive的核心原理:

keep-alive 是通过缓存组件实例来避免组件重复创建和销毁,达到性能优化的目的。它通过将已缓存的组件存储在内存中,当组件被重新激活时,直接复用之前缓存的实例,而不是重新创建。

核心原理步骤:

1.缓存实例:当组件被第一次加载时,keep-alive 会将组件的实例缓存起来。

2.组件复用:当你切换到一个已经被缓存的组件时,keep-alive 会从缓存中提取该组件的实例,而不是重新创建。

3.生命周期管理:为了处理组件的激活和停用,keep-alive 引入了 activateddeactivated 钩子,在组件进入或离开缓存时触发。

 

简单实现:

const KeepAlive = {
  name: 'keep-alive', // 定义组件的名字为 keep-alive
  abstract: true, // 将组件标记为抽象组件。抽象组件不会在父组件链中出现。

  props: {
    include: [String, RegExp, Array], // include:指定哪些组件需要被缓存,可以是字符串、正则表达式或数组。
    exclude: [String, RegExp, Array], // 指定哪些组件不需要被缓存,可以是字符串、正则表达式或数组
    max: [String, Number] // 缓存组件的最大数量
  },

  created() {
    this.cache = Object.create(null); // 用于存储缓存的组件实例。
    this.keys = []; // 用于存储缓存组件的键列表,按顺序保存。
  },

  destroyed() {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this._vnode); // 遍历 this.cache 中的所有键,并调用 pruneCacheEntry 函数清理缓存
    }
  },

  mounted() {
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name)); // 监听 include 属性的变化,并调用 pruneCache 函数清理不匹配的缓存。
    });
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name)); // 监听 exclude 属性的变化,并调用 pruneCache 函数清理匹配的缓存。
    });
  },

  render() { // 渲染函数,定义组件的渲染逻辑。
    const slot = this.$slots.default; // 获取默认插槽内容。
    const vnode = getFirstComponentChild(slot); // 获取插槽内容中的第一个子组件虚拟节点。
    const componentOptions = vnode && vnode.componentOptions; // 获取子组件的选项。

    if (componentOptions) { // 子组件的选项 存在时,继续处理。
      const name = getComponentName(componentOptions);
      const { include, exclude } = this;
      /**
       * 检查 include 和 exclude 属性。
       * 如果 include 存在且组件名称不匹配,则直接返回 vnode。
       * 如果 exclude 存在且组件名称匹配,则直接返回 vnode。
       */
      if (
        (include && (!name || !matches(include, name))) ||
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      }
      /**
       * 生成缓存键 key。
       * 如果 vnode.key 为空,则使用组件构造函数的 cid 和标签名生成键。
       * 否则,使用 vnode.key 作为键。
       */
      const key = vnode.key == null
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key;
      /**
       * 检查缓存中是否存在该键。
       * 如果存在,从缓存中取出组件实例,并将键移到 this.keys 的末尾。
       * 如果不存在,将组件虚拟节点存入缓存,并将键添加到 this.keys。
       * 如果缓存数量超过 max,则清理最早的缓存。
       */
      if (this.cache[key]) {
        vnode.componentInstance = this.cache[key].componentInstance;
        remove(this.keys, key);
        this.keys.push(key);
      } else {
        this.cache[key] = vnode;
        this.keys.push(key);
        if (this.max && this.keys.length > parseInt(this.max)) {
          pruneCacheEntry(this.cache, this.keys[0], this._vnode);
        }
      }

      vnode.data.keepAlive = true; // 标记虚拟节点为 keepAlive
    }

    return vnode || (slot && slot[0]); // 返回虚拟节点或插槽中的第一个子节点
  }
};
/**
 * 清理缓存条目。
 * 获取缓存条目 entry。
 * 如果存在且不等于当前虚拟节点,销毁组件实例。
 * 将缓存条目置为空。
 */
function pruneCacheEntry(cache, key, current) {
  const entry = cache[key];
  if (entry && (!current || entry.tag !== current.tag)) {
    entry.componentInstance.$destroy();
  }
  cache[key] = null;
}
/**
 * 根据过滤条件清理缓存。
 * 获取 cache、keys 和 _vnode。
 * 遍历缓存,获取组件名称。
 * 如果名称存在且不符合过滤条件,清理缓存条目。
 */
function pruneCache(keepAliveInstance, filter) {
  const { cache, keys, _vnode } = keepAliveInstance;
  for (const key in cache) {
    const entry = cache[key];
    if (entry) {
      const name = getComponentName(entry.componentOptions);
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, _vnode);
      }
    }
  }
}
/**
 * 检查名称是否匹配模式。
 * 如果模式是数组,检查名称是否在数组中。
 * 如果模式是字符串,拆分字符串并检查名称是否在其中。
 * 如果模式是正则表达式,测试名称是否匹配。
 * 否则返回 false。
 */
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 (pattern.test) {
    return pattern.test(name);
  }
  return false;
}
// 获取组件构造函数的 name 或标签名
function getComponentName(opts) {
  return opts && (opts.Ctor.options.name || opts.tag);
}
// 获取第一个子组件 过滤并返回第一个有 componentOptions 的子节点。
function getFirstComponentChild(children) {
  return children && children.filter(c => c && c.componentOptions)[0];
}

ps: Vue 3 的 keep-alive 原理还是缓存组件实例

posted on 2024-12-12 14:56  sss大辉  阅读(23)  评论(0编辑  收藏  举报

导航