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

vue3学习源码笔记(小白入门系列)------KeepAlive 原理

目录

  • 说明
    • 组件是如何被缓存的,什么时候被激活
    • 对于KeepAlive 中组件 如何完成激活的
    • 对于KeepAlive 中组件 如何完成休眠的
  • 总结

说明

Vue 内置了 KeepAlive 组件,实现缓存多个组件实例切换时,完成对卸载组件实例的缓存,从而使得组件实例在来会切换时不会被重复创建。

<template><KeepAlive> <component :is="xxx" /> </KeepAlive>
</template>

当动态组件在随着 xxx 变化时,如果没有 KeepAlive 做缓存,那么组件在来回切换时就会进行重复的实例化,这里就是通过 KeepAlive 实现了对不活跃组件的缓存,只有第一次加载会初始化 instance,后续会使用 缓存的 vnode 再强制patch 下 防止遗漏 有 组件 props 导致的更新,省略了(初始化 instance 和 全量生成组件dom 结构的过程)。

组件是如何被缓存的,什么时候被激活

先得看下 KeepAlive 的实现 ,它本身是一个抽象组件,会将子组件渲染出来

const KeepAliveImpl = {// 组件名称name: `KeepAlive`,// 区别于其他组件的标记__isKeepAlive: true,// 组件的 props 定义props: {include: [String, RegExp, Array],exclude: [String, RegExp, Array],max: [String, Number]},setup(props, {slots}) {// ...// setup 返回一个函数 就是 组件的render 函数 return () => {// ...}}

每次 子组件改变 就会触发 render 函数

看下 render 函数的详情

const KeepAliveImpl = {//...// cache sub tree after renderlet pendingCacheKey: CacheKey | null = nullconst cacheSubtree = () => {// fix #1621, the pendingCacheKey could be 0if (pendingCacheKey != null) {cache.set(pendingCacheKey, getInnerChild(instance.subTree))}}onMounted(cacheSubtree)onUpdated(cacheSubtree)onBeforeUnmount(() => {cache.forEach(cached => {const { subTree, suspense } = instanceconst vnode = getInnerChild(subTree)if (cached.type === vnode.type && cached.key === vnode.key) {// current instance will be unmounted as part of keep-alive's unmountresetShapeFlag(vnode)// but invoke its deactivated hook hereconst da = vnode.component!.dada && queuePostRenderEffect(da, suspense)return}unmount(cached)})})// ...setup(props, { slot }) {// ...return () => {// 记录需要被缓存的 keypendingCacheKey = null// ...// 获取子节点const children = slots.default()const rawVNode = children[0]if (children.length > 1) {// 子节点数量大于 1 个,不会进行缓存,直接返回current = nullreturn children} else if (!isVNode(rawVNode) ||(!(rawVNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) &&!(rawVNode.shapeFlag & ShapeFlags.SUSPENSE))) {current = nullreturn rawVNode}// suspense 特殊处理,正常节点就是返回节点 vnodelet vnode = getInnerChild(rawVNode)const comp = vnode.type// 获取 Component.name 值const name = getComponentName(isAsyncWrapper(vnode) ? vnode.type.__asyncResolved || {} : comp)// 获取 props 中的属性const { include, exclude, max } = props// 如果组件 name 不在 include 中或者存在于 exclude 中,则直接返回if ((include && (!name || !matches(include, name))) ||(exclude && name && matches(exclude, name))) {current = vnodereturn rawVNode}// 缓存相关,定义缓存 keyconst key = vnode.key == null ? comp : vnode.key// 从缓存中取值const cachedVNode = cache.get(key)// clone vnode,因为需要重用if (vnode.el) {vnode = cloneVNode(vnode)if (rawVNode.shapeFlag & ShapeFlags.SUSPENSE) {rawVNode.ssContent = vnode}}// 给 pendingCacheKey 赋值,将在 beforeMount/beforeUpdate 中被使用pendingCacheKey = key// 如果存在缓存的 vnode 元素if (cachedVNode) {// 复制挂载状态// 复制 DOMvnode.el = cachedVNode.el// 复制 componentvnode.component = cachedVNode.component// 增加 shapeFlag 类型 COMPONENT_KEPT_ALIVEvnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE// 把缓存的 key 移动到到队首keys.delete(key)keys.add(key)} else {// 如果缓存不存在,则添加缓存keys.add(key)// 如果超出了最大的限制,则移除最早被缓存的值if (max && keys.size > parseInt(max as string, 10)) {pruneCacheEntry(keys.values().next().value)}}// 增加 shapeFlag 类型 COMPONENT_SHOULD_KEEP_ALIVE,避免被卸载vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVEcurrent = vnode// 返回 vnode 节点return isSuspense(rawVNode.type) ? rawVNode : vnode}}
}

props.max 会确定 缓存组件的最大数量 默认没有上限,但是组件会占用内存 所以并不是 max越大越好
props.include 表示包含哪些组件可被缓存
props.exclude 表示排除那些组件

组件缓存的时机:
组件切换的时候 会保存 上一个组件的vnode 到 cache Map 中 key 是 vnode.key

组件卸载时机:
缓存组件数量超过max,会删除活跃度最低的缓存组件,或者 整个KeepAlive 组件被unmount的时候

对于KeepAlive 中组件 如何完成激活的

当 component 动态组件 is 参数发生改变时 ,

执行 KeepAlive组件 componentUpdateFn 就会执行 上一步的render 函数 会 生成 新的vnode (
然后再 走 patch
再走到 processComponent

再看下 processComponent中 针对 vnode.shapeFlag 为COMPONENT_KEPT_ALIVE(在keepalive render 函数中 组件类型 会被设置成COMPONENT_KEPT_ALIVE ) 有特殊处理

在这里插入图片描述
其中 parentComponent 其实指向的是 KeepAlive 组件, 得出 processComponent 实际调用的是 KeepAlive 组件上下文中的 activate 方法 去做挂载操作

 sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {const instance = vnode.component!// 先直接将 move(vnode, container, anchor, MoveType.ENTER, parentSuspense)// in case props have changedpatch(instance.vnode,vnode,container,anchor,instance,parentSuspense,isSVG,vnode.slotScopeIds,optimized)queuePostRenderEffect(() => {instance.isDeactivated = falseif (instance.a) {invokeArrayFns(instance.a)}const vnodeHook = vnode.props && vnode.props.onVnodeMountedif (vnodeHook) {invokeVNodeHook(vnodeHook, instance.parent, vnode)}}, parentSuspense)if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {// Update components treedevtoolsComponentAdded(instance)}}

先直接将缓存的dom 先挂载到 container 下面(节约了 重新生成dom的 时间 ),在强制patch 一下 避免遗漏 有props 改变引发的更新。这时候 缓存的组件就被激活了。

对于KeepAlive 中组件 如何完成休眠的

<template><KeepAlive> <component :is="xxx" /> </KeepAlive>
</template>

is 发生改变 会导致 上一次的组件执行unmount 操作

const unmount = (vnode, parentComponent, parentSuspense, doRemove = false) => {// ...const { shapeFlag  } = vnodeif (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)return}// ...
}

同理 也会走到。keepalive 上下文中的 deactivate 方法

sharedContext.deactivate = (vnode: VNode) => {const instance = vnode.component!move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)queuePostRenderEffect(() => {if (instance.da) {invokeArrayFns(instance.da)}const vnodeHook = vnode.props && vnode.props.onVnodeUnmountedif (vnodeHook) {invokeVNodeHook(vnodeHook, instance.parent, vnode)}instance.isDeactivated = true}, parentSuspense)if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {// Update components treedevtoolsComponentAdded(instance)}}

卸载态函数 deactivate 核心工作就是将页面中的 DOM 移动到一个隐藏不可见的容器 storageContainer 当中,这样页面中的元素就被移除了。当这一切都执行完成后,最后再通过 queuePostRenderEffect 函数,将用户定义的 onDeactivated 钩子放到状态更新流程后

总结

1.组件是通过类似于 LRU 的缓存机制来缓存的,并为缓存的组件 vnode 的 shapeFlag 属性打上 COMPONENT_KEPT_ALIVE 属性,当组件在 processComponent 挂载时,如果存在COMPONENT_KEPT_ALIVE 属性,则会执行激活函数,激活函数内执行具体的缓存节点挂载逻辑。

2.缓存不是越多越好,因为所有的缓存节点都会被存在 cache 中,如果过多,则会增加内存负担。

3.丢弃的方式就是在缓存重新被激活时,之前缓存的 key 会被重新添加到队首,标记为最近的一次缓存,如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被丢弃。

相关文章:

vue3学习源码笔记(小白入门系列)------KeepAlive 原理

目录 说明组件是如何被缓存的&#xff0c;什么时候被激活对于KeepAlive 中组件 如何完成激活的对于KeepAlive 中组件 如何完成休眠的 总结 说明 Vue 内置了 KeepAlive 组件&#xff0c;实现缓存多个组件实例切换时&#xff0c;完成对卸载组件实例的缓存&#xff0c;从而使得组…...

边写代码边学习之mlflow

1. 简介 MLflow 是一个多功能、可扩展的开源平台&#xff0c;用于管理整个机器学习生命周期的工作流程和工件。 它与许多流行的 ML 库内置集成&#xff0c;但可以与任何库、算法或部署工具一起使用。 它被设计为可扩展的&#xff0c;因此您可以编写插件来支持新的工作流程、库和…...

基于吉萨金字塔建造优化的BP神经网络(分类应用) - 附代码

基于吉萨金字塔建造优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于吉萨金字塔建造优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.吉萨金字塔建造优化BP神经网络3.1 BP神经网络参数设置3.2 吉萨金字…...

axios的post请求所有传参方式

Axios支持多种方式来传递参数给POST请求。以下是一些常见的方式&#xff1a; 作为请求体&#xff1a; 你可以将参数作为请求体的一部分&#xff0c;通常用于发送表单数据或JSON数据。例如&#xff1a; const data { key1: value1, key2: value2 }; axios.post(/api/endpoint, …...

【c++】向webrtc学比较2: IsNewerSequenceNumber 用于NackTracker及测试

LatestSequenceNumber inline uint16_t LatestSequenceNumber(uint16_t sequence_number1,uint16_t sequence_number2) {return IsNewerSequenceNumber(sequence_number1, sequence_number2)? sequence_number1: sequen...

PRCV 2023:语言模型与视觉生态如何协同?合合信息瞄准“多模态”技术

近期&#xff0c;2023年中国模式识别与计算机视觉大会&#xff08;PRCV&#xff09;在厦门成功举行。大会由中国计算机学会&#xff08;CCF&#xff09;、中国自动化学会&#xff08;CAA&#xff09;、中国图象图形学学会&#xff08;CSIG&#xff09;和中国人工智能学会&#…...

深度学习硬件配置推荐(kaggle学习)

目录 1. 基础推荐2. GPU显存与内存是一个1:4的配比&#xff1f;3. deep learning 入门和kaggle比赛4. 有些 Kaggle 比赛数据集很大&#xff0c;可能需要更多的 GPU 显存&#xff0c;请推荐显存4. GDDR6和HBM25. HDD 或 SATA SSD 1. 基础推荐 假设您作为一个深度学习入门学者的…...

1019hw

登录窗口头文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QToolBar> #include <QMenuBar> #include <QPushButton> #include <QStatusBar> #include <QLabel> #include <QDockWidget>//浮动窗口…...

两分钟搞懂UiAutomator自动化测试框架

1. UiAutomator简介 UiAutomator是谷歌在Android4.1版本发布时推出的一款用Java编写的UI测试框架&#xff0c;基于Accessibility服务。其最大的特点就是可以跨进程操作&#xff0c;可以使用UiAutomator框架提供的一些方便的API来对安卓应用进行一系列的自动化测试操作&#xf…...

Fast DDS之Subscriber

目录 SubscriberSubscriberQosSubscriberListener创建Subscriber DataReaderSampleInfo读取数据 Subscriber扮演容器的角色&#xff0c;里面可以有很多DataReaders&#xff0c;它们使用Subscriber的同一份SubscriberQos配置。Subscriber可以承载不同Topic和数据类型的DataReade…...

测试PySpark

文章最前&#xff1a; 我是Octopus&#xff0c;这个名字来源于我的中文名--章鱼&#xff1b;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github &#xff1b;这博客是记录我学习的点点滴滴&#xff0c;如果您对 Python、Java、AI、算法有兴趣&#xff0c;可以关注我的…...

C语言- 原子操作

基本概念 在C语言(尤其是C11标准之后)中,原子操作提供了一种机制,使得程序员可以在并发环境中,不使用互斥或其他同步原语,而直接对数据进行操作,同时确保数据的完整性和一致性。 原子变量和原子操作的核心思想是:无论什么时候,只有一个线程能够看到变量的修改操作。…...

设置hadoop+安装java环境

上一篇 http://t.csdnimg.cn/K3MFS 基本操作 接着上一篇 先导入之前导出的虚拟机 选择导出到对应的文件夹中 这里修改一下保存虚拟机的位置&#xff08;当然你默认也可以&#xff09; 改一个名字 新建一个share文件夹用来存放共享软件的文件夹 在虚拟机的设置中找到这个设置…...

阿里云新加坡主机服务器选择

阿里云新加坡主机有哪些选择&#xff1f;可以选择云服务器ECS或轻量应用服务器&#xff0c;都有新加坡地域可以选择&#xff0c;东南亚地区可以选择新加坡、韩国首尔、日本东京等地域&#xff0c;阿里云新加坡主机测试IP地址&#xff1a;161.117.118.93 可以测试下本地到新加坡…...

21天打卡掌握java基础操作

Java安装环境变量配置-day1 参考&#xff1a; https://www.runoob.com/w3cnote/windows10-java-setup.html 生成class文件 java21天打卡-day2 输入和输出 题目&#xff1a;设计一个程序&#xff0c;输入上次考试成绩&#xff08;int&#xff09;和本次考试成绩&#xff0…...

SQL题目记录

1.商品推荐题目 1.思路&#xff1a; 通过取差集 得出要推荐的商品差集的选取&#xff1a;except直接取差集 或者a left join b on where b null 2.知识点 1.except selectfriendship_info.user1_id as user_id,sku_id fromfriendship_infojoin favor_info on friendship_in…...

Linux程序调试器——gdb的使用

gdb的概述 GDB 全称“GNU symbolic debugger”&#xff0c;从名称上不难看出&#xff0c;它诞生于 GNU 计划&#xff08;同时诞生的还有 GCC、Emacs 等&#xff09;&#xff0c;是 Linux 下常用的程序调试器。发展至今&#xff0c;GDB 已经迭代了诸多个版本&#xff0c;当下的…...

前端打包项目上线-nginx

第一步&#xff1a;下载nginx。 直接下载 nginx/Windows-1.25.2 pgp 第二步&#xff1a;解压zip包 第三步&#xff1a;打开文件夹&#xff0c;把http里的路径打开cmd 第四步&#xff1a;打开你的http-server服务&#xff0c;没有下载去下一次就ok了 打开后就可以访问了 第五步…...

创龙瑞芯微RK3568参数修改(调试口波特率和rootfs文件)

前言 前面写了基本的文件编译、系统编译和系统烧写&#xff0c;差不多前期工作就准备的差不多了。目前的东西能解决大部分入门级的需求。当然如果需要开发的话&#xff0c;还需要修改其他东西&#xff0c;下面一步一步的给小伙伴介绍关键参数怎么修改。 给定波特率 拿到开发板…...

VMware——VMware17安装WindowServer2012R2环境(图解版)

目录 一、WindowServer2012R2镜像百度云下载二、安装 一、WindowServer2012R2镜像百度云下载 下载链接&#xff1a;https://pan.baidu.com/s/1TWnSRJTk0ruGNn4YinzIgA 提取码&#xff1a;e7u0 二、安装 打开虚拟机&#xff0c;点击【创建新的虚拟机】&#xff0c;如下图&…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

Java数值运算常见陷阱与规避方法

整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...