当前位置: 首页 > article >正文

Vue keep-alive 深度使用解读

使用方法

Vue 的 keep-alive为抽象组件,主要用于缓存内部组件数据状态。可以将组件缓存起来并在需要时重新使用,而不是每次重新创建。这可以提高应用的性能和用户体验,特别是在需要频繁切换组件时。
Props:

include

  • 字符串或正则表达式。只有名称匹配的组件会被缓存。

exclude

  • 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

keep-alive 同时存在 **include **和 **exclude ** 属性时,exclude 的优先级更高。也就是说,如果一个组件既在 exclude 中,又在include 中,那么这个组件将不会被缓存。

max

  • 数字。最多可以缓存多少组件实例。

使用LRU(Least Recently Used)算法
最近最少使用算法,LRU 算法的基本思想当缓存空间已满时,优先淘汰最近最少使用的缓存数据。

参考leetCode 146题:https://leetcode.cn/problems/lru-cache/description/

class Node {
    constructor(key = 0, value = 0) {
        this.key = key;
        this.value = value;
        this.prev = null;
        this.next = null;
    }
}

class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.dummy = new Node(); // 哨兵节点
        this.dummy.prev = this.dummy;
        this.dummy.next = this.dummy;
        this.keyToNode = new Map();
    }

    // 获取 key 对应的节点,同时把该节点移到链表头部
    getNode(key) {
        if (!this.keyToNode.has(key)) { // 没有这本书
            return null;
        }
        const node = this.keyToNode.get(key); 
        this.remove(node); 
        this.pushFront(node); 
        return node;
    }

    get(key) {
        const node = this.getNode(key);
        return node ? node.value : -1;
    }

    put(key, value) {
        let node = this.getNode(key);
        if (node) { 
            node.value = value; 
            return;
        }
        node = new Node(key, value) 
        this.keyToNode.set(key, node);
        this.pushFront(node); 
        if (this.keyToNode.size > this.capacity) { /
            const backNode = this.dummy.prev;
            this.keyToNode.delete(backNode.key);
            this.remove(backNode); 
        }
    }

    // 删除一个节点
    remove(x) {
        x.prev.next = x.next;
        x.next.prev = x.prev;
    }

    // 在链表头添加一个节点
    pushFront(x) {
        x.prev = this.dummy;
        x.next = this.dummy.next;
        x.prev.next = x;
        x.next.prev = x;
    }
}

新的生命周期:

activated: 组件被激活时调用,可以用来更新数据等操作。

deactivated: 组件被缓存时调用,可以用来清除数据等操作。

当使用activated时,再次进入页面将不会出发created钩子,若想要在数据变化时 重新触发调用接口 可以在activated中获取值,再利用watch监听值的变化 调用接口

缓存router页面

<template>
  <keep-alive>
    <router-view></router-view>
  </keep-alive>
</template>

此时可以利用路由守卫beforeRouteEnter;beforeRouteUpdate;beforeRouteLeave 判断你缓存页面的来源和去向

1.在全局app.js中 使用keep-alive包裹全局

<div class="main-content">
      <keep-alive>
        <router-view :key="$route.path" v-if="$route.meta.keepAlive"></router-view>
      </keep-alive>
      <router-view :key="$route.path" v-if="!$route.meta.keepAlive"></router-view>
 </div>

2.在路由配置文件中 添加meta:{keepAlive: true}

  {
    path: '/mine',
    component: Layout,
    children: [
      {
        path: '/mine',
        component: () => import('@/views/mine/index.vue'),
        meta: {
          title: '我的页面',
          keepAlive: true,
          scrollTop: 0
        }
      }
    ]
  }

注意

keep-alive 先匹配被包含组件的 name 字段,如果 name 不可用,则匹配当前组件 components 配置中的注册名称。

当匹配条件同时在 include 与 exclude 存在时,以 exclude 优先级最高

缓存组件

<keep-alive>
  <component :is="view"></component>
</keep-alive>

这里使用动态组件的方式,将组件引入进来。view在computed中返回

刷新keep-alive的组件

可以使用 this.$forceUpdate() 方法。但是重新渲染整个组件,包括不在 keep-alive 组件中的部分,谨慎使用,以免影响应用的性能。

或者 我们定义一个手动刷新的方法,将缓存的组件手动设置为无缓存

//template
  <keep-alive v-if="isCancelKeepAlive">
    <component :is="view"></component>
  </keep-alive>

// methods        
  cancelKeepAlive(){
      this.isCancelKeepAlive = false;
      this.$nextTick(()=>{
        this.isCancelKeepAlive = true;
      })
    },

原理

获取 keep-alive 包裹着的第一个子组件对象及其组件名; 如果 keep-alive 存在多个子元素,keep-alive 要求同时只有一个子元素被渲染。所以在开头会获取插槽内的子元素,调用 getFirstComponentChild 获取到第一个子元素的 VNode。
根据设定的黑白名单(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则开启缓存策略。
根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键)。
如果不存在,则在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key)。最后将该组件实例的keepAlive属性值设置为true。

源码

https://github.com/vuejs/vue/blob/2.6/src/core/components/keep-alive.js

/* @flow */

import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'

type CacheEntry = {
  name: ?string;
  tag: ?string;
  componentInstance: Component;
};

type CacheEntryMap = { [key: string]: ?CacheEntry };

function getComponentName (opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}

function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  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 pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const entry: ?CacheEntry = cache[key]
    if (entry) {
      const name: ?string = entry.name
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

function pruneCacheEntry (
  cache: CacheEntryMap,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const entry: ?CacheEntry = cache[key]
  if (entry && (!current || entry.tag !== current.tag)) {
    entry.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}

const patternTypes: Array<Function> = [String, RegExp, Array]

export default {
  name: 'keep-alive',
  abstract: true,

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },

  methods: {
    cacheVNode() {
      const { cache, keys, vnodeToCache, keyToCache } = this
      if (vnodeToCache) {
        const { tag, componentInstance, componentOptions } = vnodeToCache
        cache[keyToCache] = {
          name: getComponentName(componentOptions),
          tag,
          componentInstance,
        }
        keys.push(keyToCache)
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
        this.vnodeToCache = null
      }
    }
  },

  created () {
    this.cache = Object.create(null)
    this.keys = []
  },

  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    this.cacheVNode()
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  updated () {
    this.cacheVNode()
  },

  render () {
    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))
      ) {
        return vnode
      }

      const { cache, keys } = this
      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]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key)
      } else {
        // delay setting the cache until update
        this.vnodeToCache = vnode
        this.keyToCache = key
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}

业务场景举例

深入二级页面后返回的缓存

比如、列表页->详情页->列表页,列表页数据需要被缓存、在H5中、还常常存在记录列表滚动位置的需求。

切换tab缓存

比如web中、tab1->进入tab1编辑页->tab2-tab1编辑页,编辑页数据需要被缓存、保存用户填写的信息等

全局路由缓存


http://www.kler.cn/a/387391.html

相关文章:

  • 零基础玩转IPC之——海思平台实现P2P远程传输实验(基于TUTK,国科君正全志海思通用)
  • [项目代码] YOLOv5 铁路工人安全帽安全背心识别 [目标检测]
  • 5G 现网信令参数学习(3) - RrcSetup(1)
  • crond 任务调度 (Linux相关指令:crontab)
  • 动态规划 —— dp 问题-买卖股票的最佳时机IV
  • Kafka 快速入门(一)
  • 删除conda和 pip 缓存的包
  • 深度剖析RPC框架:为你的分布式应用找到最佳通信方式
  • 每天五分钟深度学习PyTorch:基于全连接神经网络完成手写字体识别
  • 深入Zookeeper节点操作:高级功能与最佳实践
  • IDA*算法 Power Calculus————poj 3134
  • 孔夫子的数字化宝库:用API解锁在售商品的秘密
  • 安装lua-nginx-module实现WAF功能
  • 瞬间对大模型与NLP的兴趣达到了1000000000%
  • 腾讯混元3D-1.0:文本到三维和图像到三维生成的统一框架
  • websphere CVE-2015-7450反序列化和弱口令,后台Getshell
  • 【赵渝强老师】Redis的AOF数据持久化
  • Spring——入门
  • MySQL 数据表常用编码类型解析
  • Java | Leetcode Java题解之第554题砖墙
  • 怎么把图片快速压缩变小?图片在线压缩的3款简单工具
  • 跨境访问难题?SD-WAN跨境加速专线加速电商社交媒体推广
  • 静态NAT和NAPT的区别
  • MySQL数据库专栏(四)MySQL数据库链接操作C#篇
  • 字节青训营刷题--完美偶数计数【简单】
  • Unity 读取文本文档 方法总结