对keep-alive的理解,它是如何实现的,具体缓存的是什么?

keep-alive 组件的作用是在组件切换时,保存一些组件的状态,防止多次渲染。它通过缓存组件的 vnode 实例来实现。

具体实现步骤如下:

  1. 创建一个缓存对象 cache 和一个存储组件 key 的数组 keys
  2. 在组件的 created 钩子函数中初始化缓存对象和数组。
  3. 在组件的 destroyed 钩子函数中销毁缓存对象。
  4. 在组件的 mounted 钩子函数中监听 includeexclude 属性的变化,并根据最新的属性值实时削减缓存的组件。
  5. 在组件的 render 函数中,判断当前组件是否需要缓存。
    • 如果不需要缓存,则直接返回组件的 vnode 实例。
    • 如果需要缓存,则获取组件的 key,并判断缓存对象中是否存在该 key。
      • 如果存在,则将缓存对象中对应的组件实例赋给 vnode,并更新组件在数组中的位置(LRU 缓存策略)。
      • 如果不存在,则将组件的 vnode 实例添加到缓存对象中,并将组件的 key 添加到数组中,并判断数组长度是否超过最大缓存数量,如果超过,则移除最早未使用的组件。
  6. 最后将组件的 keepAlive 属性设置为 true,表示该组件需要被缓存。

需要注意的是,keep-alive 只对第一个子组件有效,且只有在组件的 created 钩子函数中才会执行组件的初始化操作,后续的更新操作会通过 patch 过程直接插入缓存的 DOM 对象,不会再执行组件的 createdmounted 钩子函数。

使用 keep-alive 组件的示例:

<template>
  <div>
    <button @click="toggleComponent">Toggle Component</button>
    <keep-alive>
      <component :is="currentComponent"></component>
    </keep-alive>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentComponent: 'ComponentA',
    };
  },
  methods: {
    toggleComponent() {
      this.currentComponent = this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA';
    },
  },
  components: {
    ComponentA: {
      template: '<div>Component A</div>',
    },
    ComponentB: {
      template: '<div>Component B</div>',
    },
  },
};
</script>

在上述示例中,有两个组件 ComponentA 和 ComponentB。通过点击按钮,可以切换显示的组件。由于这两个组件被包裹在 keep-alive 组件中,所以在切换组件时,组件的状态会被保留,不会重新渲染。

当初始显示 ComponentA 时,点击按钮后会切换到 ComponentB。再次点击按钮,会切换回 ComponentA。在切换过程中,组件的状态会被保留,不会重新渲染。

这个示例展示了 keep-alive 组件的作用,它可以在组件切换时保存组件的状态,避免多次渲染,提高性能。

<keep-alive> 组件

<keep-alive> 是 Vue.js 中的一个抽象组件,用于缓存动态组件。当使用 <keep-alive> 包裹动态组件时,这些组件将会被缓存,而不是每次切换都重新渲染。

属性

<keep-alive> 有以下三个属性:

  • include: 字符串或正则表达式,只有名称匹配的组件会被缓存。
  • exclude: 字符串或正则表达式,任何名称匹配的组件都不会被缓存。
  • max: 数字,最多可以缓存多少组件实例。

工作流程

  1. 当包裹动态组件时,<keep-alive> 会监听动态组件的生命周期钩子。
  2. 当切换到一个新的动态组件时,如果该组件匹配 include 并且不匹配 exclude,它将会被缓存。
  3. 如果组件被缓存,它的状态将会被保留,包括已经触发的生命周期钩子和当前的数据状态。
  4. 当再次切换回已经被缓存的组件时,Vue.js 将会重新激活这个组件,并且不会再次创建它。
  5. 如果设置了 max 属性,当缓存组件数量超过 max 值时,将会按照一定策略清除缓存中的组件实例。

通过这种方式,可以提高应用性能,因为在切换组件时,不需要每次都销毁和重新创建组件,而是直接从缓存中读取已存在的组件状态。

render 函数

render () {
  //
  function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
    if (Array.isArray(children)) {
  for (let i = 0; i < children.length; i++) {
    const c = children[i]
    if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
      return c
    }
  }
  }
  }
  const slot = this.$slots.default // 获取默认插槽
  const vnode: VNode = getFirstComponentChild(slot)// 获取第一个子组件
  const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions // 组件参数
  if (componentOptions) { // 是否有组件参数
    // check pattern
    const name: ?string = getComponentName(componentOptions) // 获取组件名
    const { include, exclude } = this
    if (
      // not included
      (include && (!name || !matches(include, name))) ||
      // excluded
      (exclude && name && matches(exclude, name))
    ) {
      // 如果不匹配当前组件的名字和include以及exclude
      // 那么直接返回组件的实例
      return vnode
    }

    const { cache, keys } = this

    // 获取这个组件的key
    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]) {
      // LRU缓存策略执行
      vnode.componentInstance = cache[key].componentInstance // 组件初次渲染的时候componentInstance为undefined

      // make current key freshest
      remove(keys, key)
      keys.push(key)
      // 根据LRU缓存策略执行,将key从原来的位置移除,然后将这个key值放到最后面
    } else {
      // 在缓存列表里面没有的话,则加入,同时判断当前加入之后,是否超过了max所设定的范围,如果是,则去除
      // 使用时间间隔最长的一个
      cache[key] = vnode
      keys.push(key)
      // prune oldest entry
      if (this.max && keys.length > parseInt(this.max)) {
        pruneCacheEntry(cache, keys[0], keys, this._vnode)
      }
    }
    // 将组件的keepAlive属性设置为true
    vnode.data.keepAlive = true // 作用:判断是否要执行组件的created、mounted生命周期函数
  }
  return vnode || (slot && slot[0])
}

这段代码是关于 Vue 中 keep-alive 组件的 render 函数的实现。它描述了 keep-alive 是如何通过缓存来存储组件的 vnode 实例,并根据一定的策略来管理这些缓存的过程。

具体而言,这段代码做了以下几件事情:

  1. 获取 keep-alive 下第一个子组件的实例对象,并获取该组件的组件名。
  2. 通过当前组件名匹配 include 和 exclude,判断当前组件是否需要缓存。如果不需要缓存,则直接返回当前组件的实例 vnode。
  3. 如果需要缓存,判断当前组件是否在缓存数组里:
    • 如果存在,则将原位置上的 key 从数组中移除,同时将这个组件的 key 放到数组的最后面(LRU)。
    • 如果不存在,则将组件的 key 放入数组。然后判断当前 key 数组是否超过了 max 所设置的范围,如果超过,则删除未使用时间最长的一个组件的 key。
  4. 最后,将这个组件的 keepAlive 属性设置为 true。

这个实现使得 keep-alive 能够根据一定的策略来管理组件的缓存,以提高页面性能和用户体验。

keep-alive 本身的创建过程和 patch 过程

首次渲染
● 组件的首次渲染∶判断组件的 abstract 属性,才往父组件里面挂载 DOM

// core/instance/lifecycle
function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) { // 判断组件的abstract属性,才往父组件里面挂载DOM
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

判断当前 keepAlive 和 componentInstance 是否存在来判断是否要执行组件 prepatch 还是执行创建 componentlnstance

// core/vdom/create-component
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) { // componentInstance在初次是undefined!!!
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode) // prepatch函数执行的是组件更新的过程
    } else {
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

这段描述涉及了 Vue 中 keep-alive 组件的创建和 patch 过程,以及在缓存渲染时如何处理组件的生命周期钩子函数。

首先,描述了首次渲染过程:

  • 在组件的首次渲染中,会判断组件的 abstract 属性,只有当该属性为 false 时才会将 DOM 挂载到父组件中。
  • 然后,在初始化阶段,根据当前的 keepAlivecomponentInstance 是否存在来判断是否要执行组件的 prepatch 过程还是执行创建 componentInstance。如果 componentInstance 在初次渲染时是 undefined,则会执行创建 componentInstance 的操作。

接着,描述了缓存渲染时的处理过程:

  • 当进行缓存渲染时,会根据 vnode.componentInstance(首次渲染时为 undefined)和 keepAlive 属性来判断是否执行组件的 createdmounted 等生命周期钩子函数。如果组件被缓存,那么不会执行这些生命周期钩子函数,而是直接对缓存的组件执行 patch 过程:将缓存的 DOM 对象直接插入到目标元素中,完成数据更新的渲染过程。

这个描述总结了 Vue 中 keep-alive 组件在首次渲染和缓存渲染时的行为,以及它是如何管理组件的生命周期钩子函数和 DOM 渲染的过程。

LRU (least recently used)缓存策略

LRU(Least Recently Used)缓存策略是基于数据的历史访问记录来淘汰数据的算法。其核心思想是,如果数据最近被访问过,那么将来被访问的几率也更高。

常见的实现方式是使用一个链表保存缓存数据,并按照以下步骤进行操作:

  1. 当新数据插入时,将其插入到链表的头部。
  2. 每当缓存命中(即缓存数据被访问),就将对应的数据移到链表的头部。
  3. 当链表已满时,将链表尾部的数据丢弃,以便为新数据腾出空间。

通过这种方式,LRU 算法能够有效管理缓存数据,保留最常用的数据,并在需要时清理不常用的数据,以提高缓存的命中率和性能。

总结:keep-alive 组件通过缓存组件的 vnode 实例来保存组件的状态,实现了组件切换时的状态保持。它的实现原理是使用缓存对象和数组来存储组件实例,并根据缓存策略(LRU)来管理缓存的组件数量。

持续学习总结记录中,回顾一下上面的内容:
对 keep-alive 的理解:
<keep-alive> 是 Vue 提供的一个抽象组件,用于在组件之间切换时缓存组件的状态或 DOM。当组件被切换出去时,会被 <keep-alive> 缓存起来,而不是被销毁,这样可以提高性能和用户体验。
它是如何实现的:
<keep-alive> 利用了 Vue 的抽象组件能力,在内部维护一个缓存对象,用于存储被缓存组件的状态和 DOM 结构。当需要缓存的组件被激活时,从缓存对象中检索相应的组件,如果存在,则直接复用之前的状态和 DOM,而不需要重新创建。
具体缓存的是什么:
<keep-alive> 缓存的是组件的实例及其状态、数据以及渲染出的 DOM 结构。这样,在组件被再次激活时,可以直接从缓存中获取到这些信息,而不需要重新初始化组件及其状态,从而提高了性能。

Logo

前往低代码交流专区

更多推荐