Vue keep-alive 深度使用解读

news2024/11/8 20:12:12

使用方法

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.coloradmin.cn/o/2236059.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何简化App Store提现?——作为游戏开发者的跨境收款体验分享

目录 如何简化App Store提现&#xff1f;——作为游戏开发者的跨境收款体验分享跨境收款常见的几个问题使用万里汇收款后的体验1. 结算流程简单&#xff0c;到账更快2. 多场景收付更灵活3. 多种支付方式支持 使用后的效果&#xff1a;资金管理更高效个人建议 如何简化App Store…

sql报错信息将字符串转换为 uniqueidentifier 时失败

报错信息&#xff1a; [42000] [Microsoft][SQL Server Native Client 10.0][SQL Server]将字符串转换为 uniqueidentifier 时失败 出错行如下&#xff1a; 表A.SourceCode 表B.ID 出错原因&#xff1a; SourceCode是nvarchar,但ID是uniqueidentifier 数据库查询字段和类…

【简信CRM-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

DevOps业务价值流:架构设计最佳实践

系统设计阶段作为需求与研发之间的桥梁&#xff0c;在需求设计阶段的原型设计评审环节&#xff0c;尽管项目组人员可能未完全到齐&#xff0c;但关键角色必须到位&#xff0c;包括技术组长和测试组长。这一安排旨在同步推进两项核心任务&#xff1a;一是完成系统的架构设计&…

分享:文本转换工具:PDF转图片,WORD转PDF,WORD转图片

前言 鉴于网上大多数在线转换工具要么需要收费&#xff0c;要么免费后但转换质量极差的情况&#xff0c;本人开发并提供了PDF转图片&#xff0c;WORD转PDF&#xff0c;WORD转图片等的文本转换工具。 地址 http://8.134.236.93/entry/login 账号 账号&#xff1a;STAR001&a…

sublime可以写python吗

首先你需要安装一个Sublime Text&#xff08;http://www.sublimetext.com/&#xff09;和一个Python&#xff08;https://www.python.org/downloads/&#xff09;&#xff0c; 接下来打开Sublime Text&#xff1a; 1、如下图所示&#xff0c;点击菜单栏中的Tools —> Buil…

聊一聊Elasticsearch的基本原理与形成机制

1、搜索引擎的基本原理 通常搜索引擎包括&#xff1a;数据采集、文本分析、索引存储、搜索等模块&#xff0c;它们之间的协作流程如下图&#xff1a; 数据采集模块负责采集需要搜索的数据源。 文本分析模块是将结构化数据中的长文本切分成有实际意义的词&#xff0c;这样用户…

IO流篇(一、File)

目录 一、学习前言 二、文件简介 三、文件使用 1. 绝对路径 vs 相对路径 2. 路径分隔符 3. 属性&#xff08;字段&#xff09; 4. 构造方法 5. 常用方法 5.1. 获取文件的相关信息 5.2. 判断功能 5.3. 新建和删除 5.4. 文件的获取 5.5. 重命名文件 四、文件使用练习…

【部署glm4】属性找不到、参数错误问题解决(思路:修改模型包版本)

前言&#xff1a;在部署glm时&#xff0c;遇到了一些属性找不到、参数错误的问题&#xff0c;通常遇到这种问题都是因为模型包版本问题导致的&#xff0c;要注意模型版本是否可用。 【运行官方vllm_cli_demo.py】报错 GLM-4: [rank0]: Traceback (most recent call last): [ran…

全双工通信协议WebSocket——使用WebSocket实现智能学习助手/聊天室功能

一.什么是WebSocket&#xff1f; WebSocket是基于TCP的一种新的网络协议。它实现了浏览器与服务器的全双工通信——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并进行双向数据传输 HTTP 协议是一种无状态的、无连接的、单向的应用…

【Linux】冯诺依曼体系、再谈操作系统

目录 一、冯诺依曼体系结构&#xff1a; 1、产生&#xff1a; 2、介绍&#xff1a; 二、再谈操作系统&#xff1a; 1、为什么要管理软硬件资源&#xff1a; 2、操作系统如何进行管理&#xff1a; 3、库函数&#xff1a; 4、学习操作系统的意义&#xff1a; 一、冯诺依曼…

Linux的目录结构 | 命令的认识 | 相对路径 | 绝对路径 | 常用命令(一)

文章目录 1.Linux的目录结构2.命令的认识3.相对路径和绝对路径4.常用命令&#xff08;目录文件操作&#xff09;5.常用命令&#xff08;文本查看&#xff09; 1.Linux的目录结构 \ &#xff1a;根目录 root&#xff1a;root用户的工作目录 home&#xff1a;普通用户的工作目录 …

linux nvidia/cuda安装

1.查看显卡型号 lspci |grep -i vga2.nvidia安装 2.1在线安装 终端输入&#xff08;当显卡插上之后&#xff0c;系统会有推荐的安装版本&#xff09; ubuntu-drivers devices可得到如下内容 vendor : NVIDIA Corporation model : TU104GL [Tesla T4] driver : nvid…

简单又便宜的实现电脑远程开机唤醒方法

现有的远程开机方案 1&#xff09;使用向日葵开机棒 缺点是比较贵一点&#xff0c;开机棒要一百多&#xff0c;而且查了评论发现挺多差评说不稳定&#xff0c;会有断联和无法唤醒的情况&#xff0c;而且设置也麻烦&#xff0c;还需要网卡支持WOL 2&#xff09;使用远程开机卡 …

容器架构-Docker的成长之路

目录 1. 什么是容器 2. 容器 vs 虚拟机 3. Docker极速上手指南 环境准备 3.1 配置docker源 3.2 下载镜像加速的配置 3.3 下载自动补全工具 4. Docker C/S架构 5. Docker的镜像管理 5.1 下载nginx:alpine镜像并查看 5.2 sl大法 5.3 删除镜像 5.4 镜像清理用的命令 5…

【开源社区】ELK 磁盘异常占用解决及优化实践

1、问题及场景描述 本文主要讨论在 CentOS环境下基于 rpm 包部署 ELK 系统磁盘异常占用的问题解析和解决方案。 生产问题描述&#xff1a;以下问题现实场景基于ELK体系下&#xff0c;ES服务的磁盘占用问题解析。默认情况下&#xff0c;基于 RPM 安装的 Elasticsearch 服务的安…

仪表板展示|DataEase看中国:历年双十一电商销售数据分析

背景介绍 2024年“双十一”购物季正在火热进行中。自2009年首次推出至今&#xff0c;“双十一”已经成为中国乃至全球最大的购物狂欢节&#xff0c;并且延伸到了全球范围内的电子商务平台。随着人们消费水平的提升以及电子商务的普及&#xff0c;线上销售模式也逐渐呈现多元化…

【深度学习】论文笔记:空间变换网络(Spatial Transformer Networks)

博主简介&#xff1a;努力学习的22级计算机科学与技术本科生一枚&#x1f338;博主主页&#xff1a; Yaoyao2024往期回顾&#xff1a; 【机器学习】有监督学习由浅入深讲解分类算法Fisher算法讲解每日一言&#x1f33c;: 今天不想跑&#xff0c;所以才去跑&#xff0c;这才是长…

基于java+SpringBoot+Vue的旅游管理系统设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…

【HarmonyOS】not supported when useNormalizedOHMUrl is not true.

【HarmonyOS】 not supported when useNormalizedOHMUrl is not true. 问题背景&#xff1a; 集成三方库编译时&#xff0c;IDE提示报错信息如下&#xff1a; hvigor ERROR: Bytecode HARs: [cashier_alipay/cashiersdk] not supported when useNormalizedOHMUrl is not true…