Vue中KeepAlive 原理与源码分析

news2024/12/27 15:14:11

概念

keep-alive是Vue的一个内置实例,用于缓存组件。当keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是摧毁它们。keep-alive 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链之中。如需要在两个组件之间来回切换,或者从一个分类页跳转到详情页,之后再返回分类页,仍然可以保持上次的状态,又或者在浏览首页时重新切换回来,滚动条会保持在上次位置(等等),这时候我们就可以用到keep-alive组件。

作用

  • 在组件切换的过程中将状态保留在内存。 * 防止重复渲染DOM,减少加载时间以及性能的消耗,提高用户的体验感。 Props(参数)
    =========

  • include - string | RegExp | Array。只有名称匹配的组件会被缓存。

  • exclude - string | RegExp | Array。任何名称匹配的组件都不会被缓存。

  • max - number | string。最多可以缓存多少组件实例。

生命周期函数

activated : 在组件激活的时候使用,第一次激活是在mounted之后执行。

deactivated : 在失效的时候使用。

注意 :

  • 被包含在keep-alive中创建的组件,会多出两个生命周期的钩子:activated和deactivated
  • 使用keep-alive会将数据保留在内存中,如果要在每次进入页面的时候获取最新数据,需要在activated阶段获取数据,承担原来created钩子函数中获取数据的任务。

用法

  • keep-alive的使用只需要在动态组件的最外层添加标签即可。
在动态组件中的应用
<!-- 基本 -->
<keep-alive><component :is="choose"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive><comp-a v-if="a > 1"></comp-a><comp-b v-else></comp-b>
</keep-alive> 
在vue-router中的应用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
 <router-view></router-view>
</keep-alive> 

实现原理

步骤解析

通过 keep-alive 组件插槽,获取第一个子节点。根据 includeexclude 判断是否需要缓存,通过组件的 key,判断是否命中缓存。利用 LRU 算法,更新缓存以及对应的 keys 数组。根据 max 控制缓存的最大组件数量。

源码分析

基本结构

export declare const KeepAlive: {new (): {$props: VNodeProps & KeepAliveProps;};__isKeepAlive: true;
};

export declare type VNodeProps = {key?: string | number | symbol;ref?: VNodeRef;ref_for?: boolean;ref_key?: string;onVnodeBeforeMount?: VNodeMountHook | VNodeMountHook[];onVnodeMounted?: VNodeMountHook | VNodeMountHook[];onVnodeBeforeUpdate?: VNodeUpdateHook | VNodeUpdateHook[];onVnodeUpdated?: VNodeUpdateHook | VNodeUpdateHook[];onVnodeBeforeUnmount?: VNodeMountHook | VNodeMountHook[];onVnodeUnmounted?: VNodeMountHook | VNodeMountHook[];
};

export declare interface KeepAliveProps {include?: MatchPattern;exclude?: MatchPattern;max?: number | string;
}


declare type MatchPattern = string | RegExp | (string | RegExp)[]; 

主要流程

keep-alive 组件的实现中,使用了 Map 对象和 Set 对象来实现组件的缓存,如下代码所示:

// 创建一个缓存对象
const cache: Cache = new Map()
// 存储组件的 key
const keys: Keys = new Set() 

其中 Map 对象的键是组件选项对象,即 vnode.type 属性的值,而该 Map 对象的值是用于描述组件的 vnode 对象。由于用于描述组件的 vnode 对象存在对组件实例的引用 (即 vnode.component属性),所以缓存 vnode 对象,就等价与缓存了组件实例。

Set 对象存储的是组件对象的 keySet 对象具有自动去重的功能,因此Set对象中存储重复的组件对象。

keep-alive 组件中对缓存的管理时,首先会在组件的 onMountedonUpdated 生命周期钩子函数中设置缓存,如下代码所示:

let pendingCacheKey: CacheKey | null = null
const cacheSubtree = () => {if (pendingCacheKey != null) {// 设置缓存cache.set(pendingCacheKey, getInnerChild(instance.subTree))}
}
// 执行生命周期钩子函数
onMounted(cacheSubtree)
onUpdated(cacheSubtree) 

然后在keep-alive组件返回的函数中根据 vnode 对象的 key 去缓存中查找是否有缓存的组件,如果缓存存在,则继承组件实例,并将用于描述组件的 vnode 对象标记为 COMPONENT_KEPT_ALIVE,这样渲染器就不会重新创建新的组件实例;如果缓存不存在,则将 vnode 对象的key 添加到 keys 集合中,然后判断当缓存数量超过指定阈值时就对缓存进行修剪。如下代码所示:

return () => {// 如果 vnode 上不存在 key,则使用 vnode.type 作为keyconst key = vnode.key == null ? comp : vnode.key// 根据 vnode 的key去缓存中查找是否有缓存的组件const cachedVNode = cache.get(key)if (vnode.el) {vnode = cloneVNode(vnode)if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {rawVNode.ssContent = vnode}}the normalized vnode) inpendingCacheKey = keyif (cachedVNode) {// 如果缓存中存在缓存的组件vnode.el = cachedVNode.el// 继承缓存的组件vnode.component = cachedVNode.component// 如果组件上有动画,处理动画if (vnode.transition) {setTransitionHooks(vnode, vnode.transition!)}vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVEkeys.delete(key)keys.add(key)} else {// 将 vnode 的key 添加到 keys 集合中keys.add(key)// 当缓存数量超过指定阈值时对缓存进行修剪if (max && keys.size > parseInt(max as string, 10)) {pruneCacheEntry(keys.values().next().value)}}
} 

Vue.js 中采用的修剪策略叫作 “最新一次访问”,其核心在于,把当前访问 (或渲染) 的组件作为最新一次渲染的组件,并且该组件在缓存修剪过程中始终是安全的,即不会被修剪。如下面的代码所示:

function pruneCacheEntry(key: CacheKey) {// 从缓存对象中获取缓存的 VNodeconst cached = cache.get(key) as VNode// 如果没有当前渲染的组件或当前渲染的组件和缓存中的组件不是同一个,卸载缓存中的组件if (!current || cached.type !== current.type) {unmount(cached)} else if (current) {// 当前活动实例不应再保持活动状态。resetShapeFlag(current)}cache.delete(key)keys.delete(key)
} 

总结

keep-alive 组件的作用类似于 HTTP 中的持久链接。它可以避免组件实例不断地被销毁和重建。keep-alive 的实现并不复杂。当被 keep-alive 的组件在卸载的时候,渲染器并不会真的将其卸载,而是会将该组件搬运到一个隐藏的容器中,从而使得组件可以维持当前状态。当被 keep-alive 的组件再次被 “挂载” 时,渲染器也不会真的挂载它,而是将它从隐藏容器中搬运到原容器。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2176.html

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

相关文章

网课查题API接口-搜题api

网课查题API接口-搜题api 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#x…

小学生python游戏开发pygame--设置内容整理

游戏开发&#xff0c;相关设置内容单独放在一个文件中 如长宽&#xff0c;大小&#xff0c;颜色等起名shezhi.py&#xff0c;如下&#xff1a; # _*_ coding: UTF-8 _*_ # 开发团队&#xff1a; 信息化未来 # 开发人员&#xff1a; Administrator # 开发时间&#xff1a;2022…

微信小程序中如何实现省市区街道四级地址级联选择

大家好&#xff0c;我是雄雄。 前言 微信小程序中支持省市区地址级联吗&#xff1f;微信小程序中的地址级联最多支持到几级&#xff1f; 今天&#xff0c;我们就来看看&#xff0c;微信小程序中的地址级联的使用&#xff0c;以及一些坑…希望大家看完之后能避免踩坑啊。 省市…

JS(受人以鱼 不如受人以渔)第十七课

比你优秀的人都比你努力&#xff0c;你有什么理由不去努力。基础来自己的累秒累天累月的积累 没有一个人是从天而降的天才&#xff0c;也没有哪个人想做一个一生贫庸的人。今天我想说 受人以鱼 不如受人以渔 推荐几个官网可以自己主动去学习 JavaScript中文网-JavaScript教程…

虚拟物品(游戏)交易平台的设计与实现(Java+SSM+MySQL)

目 录 摘 要 I Abstract II 第1章 绪论 1 1.1选题背景及意义 1 1.2研究现状 1 1.3研究主要内容 2 第2章 相关技术介绍 4 1.1 SSM的技术原理 4 1.1.1 SSM语言及其特点 4 1.1.2 Java及Java Servlets概述 6 1.1.3 JavaBean简介 6 1.2 服务器配置 7 1.2.1 Tomcat安装及配置 7 1.2.2…

工业控制系统所面临的安全威胁

年新增漏洞严重程度分析 因本文收集处理的公开漏洞基本上都被 CVE 所收录&#xff0c; 所以本文在分析这些漏洞的严重 性时&#xff0c;将主要根据 CVE 的 CVSS 评估值①来判断&#xff0c;并划分为高、中、低三种情况。 根据图 2.9 的统计分析 &#xff0c;2013 年的新增漏洞…

MySQL索引优化

索引优化 EXPLAIN查询分析 参考另一篇&#xff0c;通过explain我们可以确定查询语句的访问类型&#xff0c;用到的索引&#xff0c;扫描行数以及extra信息。 回表查询与覆盖索引 InnoDB索引有聚簇索引和辅助索引。聚簇索引的叶子节点存储行记录&#xff0c;InnoDB必须要有&…

sql查询面试题复习

基础 查所有的数据 select * from user_profile;查询某几列 select device_id,gender,age,university from user_profile;取出学校的去重数据。 select distinct university from user_profile; #distinct 关键字查看前2个用户明细设备ID数据 select device_id from user_p…

反射、枚举和lambda表达式

文章目录一、反射反射的相关类获得Class对象的三种方法反射的基本使用反射优点和缺点二、枚举背景及定义枚举的使用为什么Enum源码中找不到values()方法&#xff1f;枚举与反射总结三、Lambda表达式函数式接口Lambda表达式的基本使用lambda在集合中的应用总结一、反射 Java的反…

SQL(及存储过程)跑得太慢怎么办?

SQL作为目前最常用的数据处理语言&#xff0c;广泛应用于查询、跑批等场景。当数据量较大时&#xff0c;使用SQL&#xff08;以及存储过程&#xff09;经常会发生跑得很慢的情况&#xff0c;这就要去优化SQL。优化SQL有一些特定的套路&#xff0c;通常先要查看执行计划来定位SQ…

性能优化:MySQL使用优化(1)

参考资料&#xff1a; 《explain | 索引优化的这把绝世好剑&#xff0c;你真的会用吗&#xff1f;》 《一张图彻底搞懂MySQL的 explain》 《MySQL 性能优化神器 Explain 使用分析》 《MySQL索引应用篇&#xff1a;建立索引的正确姿势与使用索引的最佳指南&#xff01;》 《…

【Node.js】npm与包【万字教学~超超超详细】

✍️ 作者简介: 前端新手学习中。 &#x1f482; 作者主页: 作者主页查看更多前端教学 &#x1f393; 专栏分享&#xff1a;css重难点教学 Node.js教学 从头开始学习 目录 包 什么是包 包的来源 为什么需要包 从哪里下载 如何下载包 npm初体验 格式化时间的传统做法 实现…

Win11系统C++将ONNX转换为engineer文件,TensorRT加速推理

准确工作&#xff0c;安装配置好CUDA,cudnn&#xff0c;vs2019&#xff0c;TensorRT 可以参考我博客&#xff08;下面博客有CUDA11.2,CUDNN11.2&#xff0c;vs2019&#xff0c;TensorRT配置方法&#xff09; (70条消息) WIN11CUAD11.2vs2019tensorTR8.6Yolov3/4/5模型加速_Ve…

【JavaDS】HashMap与HashSet的底层原理

✨博客主页: 心荣~ ✨系列专栏:【Java实现数据结构】 ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. HashMap底层原理1. HashMap的属性2. HashMap的构造方法3. 给HashMap分配内存的时机4. HashMap中的put5. HashMap中的哈希函数6. HashMap的扩容机制二. HashSet的底层原…

【资损】业务产品分析资损防控规范

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;专注于研究 Java/ Liunx内核/ C及汇编/计算机底层原理/源码&#xff0c;就职于大型金融公司后端高级工程师&#xff0c;擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。 &#x1…

读书 | 设计模式之禅 - 责任链模式

文章目录1. 实现古代妇女的“三从”制度2. 实现古代妇女的“三从”制度-优化3. 责任链模式的定义1. 实现古代妇女的“三从”制度 一位女性在结婚之前要听从于父亲&#xff0c;结婚之后要听从于丈夫&#xff0c;如果丈夫死了还要听从于儿子。作为父亲、丈夫或儿子&#xff0c;只…

Spring-day01 spring全家桶,如何学习框架,单元测试

Spring学习-概述 1. spring全家桶&#xff1a;spring &#xff0c; springmvc &#xff0c;spring boot , spring cloud spring: 出现是在2002左右&#xff0c;解决企业开发的难度。减轻对项目模块之间的管理&#xff0c; 类和类之间的管理&#xff0c; 帮助开发人员创建对象&a…

Linux:iptables和firewalld基础j解析

目录 1、四表五链概念&#xff1a; 2、数据报文流程 3、iptables 与 firewalld 区别 4、DROP 和 REJECT策略的区别&#xff1a; 5、iptables命令参数 6、iptables基本的命令使用 7.firewalld&#xff1a;基于CLI&#xff08;命令行界面&#xff09;和基于GUI&#xff08;图…

多线程wait()和notify()方法详解

多线程wait()和notify()方法详解 文章目录多线程wait()和notify()方法详解前言一、线程间等待与唤醒机制二、等待方法wait()三、唤醒方法notify()四、关于wait和notify内部等待问题&#xff08;重要&#xff09;五、完整代码&#xff08;仅供测试用&#xff09;六、wait和sleep…

docker实战学习2022版本(六)之Dockerfile整合微服务实战

需求&#xff1a;通过idea新建一个普通微服务模块&#xff1b;然后通过Dockerfile发布微服务部署到docker容器 step1&#xff1a;新建一个springboot项目&#xff0c;添加依赖 <dependencies><!--SpringBoot通用依赖模块--><dependency><groupId>org.…