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 会被重新添加到队首,标记为最近的一次缓存,如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被丢弃。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

竞赛 深度学习+python+opencv实现动物识别 - 图像识别

文章目录 0 前言1 课题背景2 实现效果3 卷积神经网络3.1卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 inception_v3网络5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; *…

DHorse v1.4.2 发布,基于 k8s 的发布平台

版本说明 优化特性 在集群列表增加集群版本&#xff1b;修改Jvm的GC指标名&#xff1b; 解决问题 解决shell脚本换行符的问题&#xff1b;解决部署历史列表页&#xff0c;环境名展示错误的问题&#xff1b;解决指标收集功能的异常&#xff1b; 升级指南 升级指南 DHorse…

零宽空格引发的问题

有人跟我反馈说有bug。 我说&#xff1a;啥bug&#xff1f; 对方说&#xff1a;刚申请的内部用户的账号登录不上去。 我说&#xff1a;还有这种事&#xff0c;报啥错&#xff1f; 登录的时候报了这个错&#xff1a; 我一看还好还好&#xff0c;跟上一次不一样的错&#xff…

“探寻服务器的无限潜能:从创意项目到在线社区,你会做什么?”

文章目录 每日一句正能量前言什么是服务器&#xff1f;服务器能做什么&#xff1f;服务器怎么用&#xff1f;部署创意项目&#xff0c;还是在线社区亦或做其他的&#xff1f;后记 每日一句正能量 未知的下一秒&#xff0c;千万不要轻言放弃。 前言 在数字化时代&#xff0c;服…

SpringBoot面试题7:SpringBoot支持什么前端模板?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:SpringBoot支持什么前端模板? Spring Boot支持多种前端模板,其中包括以下几种常用的: Thymeleaf:Thymeleaf是一种服务器端Java模板引擎,能够…

基于马尔可夫随机场的图像去噪算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1、马尔可夫随机场的基本原理 4.2、基于马尔可夫随机场的图像去噪算法 5.算法完整程序工程 1.算法运行效果图预览 原图&#xff1a; 加入噪声的图像&#xff1a; 滤波后的图像 迭代过程…

2-k8s-控制器介绍

文章目录 一、控制器类型二、Deployment控制器三、SatefulSet控制器四、Daemonset控制器五、Job控制器六、CronJob 控制器 一、控制器类型 Deployment&#xff1a;适合无状态的服务部署StatefullSet&#xff1a;适合有状态的服务部署DaemonSet&#xff1a;一次部署&#xff0c…

关于ts的keyof

type props_type {name: string,age: number }const props: props_type {name: tjq,age: 18 }for (const key in props) { //props[key]出现红色波浪线const value props[key]; }why&#xff1f; 经过我查阅多方资料&#xff0c;在网上看到一个比较合适的例子 地址&#xf…

OpenRemote: Java 开源 IoT 物联网开发平台,匹配智慧城市、智能家居、能源管理

OpenRemote 是一个直观、用户友好的基于Java语言的开源 IoT 物联网设备管理平台&#xff0c;它包括从连接设备到构建应用程序和特定领域的智能应用程序的所有功能和特性。通过OpenRemote物联网平台&#xff0c;用户可以收集和处理来自不同设备的传感器数据&#xff0c;适用于智…

Gson反序列化原理

前言 序列化和反序列化是日常工作中经常使用的工具&#xff0c;一般用于对象的持久化保存或者对象的网络传输&#xff0c;一般有两种情况&#xff0c;一种是对象本身实现了Serializable接口&#xff0c;这种情况下可以利用jdk自带的功能或者Kryo等这种封装好的序列化反序列化工…

Elasticsearch:什么是大语言模型 (LLMs)?

假设你想参加流行的游戏节目 Jeopardy&#xff08;这是一个美国电视游戏节目&#xff0c;参赛者将获得答案并必须猜测问题&#xff09;。 要参加演出&#xff0c;你需要了解任何事情的一切。 所以你决定在接下来的三年里每天都花时间阅读互联网上的所有内容。 你很快就会意识到…

关于 Invalid bound statement (not found): 错误的解决

关于 Invalid bound statement not found: 错误的解决 前言错误原因解决方法1. 检查SQL映射文件2. 检查MyBatis配置3. 检查SQL语句4. 检查命名约定5. 清除缓存6. 启用日志记录 重点注意 结语 我是将军我一直都在&#xff0c;。&#xff01; 前言 当开发Java Spring Boot应用程…

挚文集团:股票回购速度、收入指引均不及预期,令投资者失望

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 挚文集团未来将不再公布MAU数据 今年6月初&#xff0c;挚文集团(MOMO)在公布2023年第一季度业绩时透露&#xff0c;“陌陌应用的月活跃用户(MAU)”已经从去年3月的1.109亿下降到了今年3月的1.065亿&#xff0c;同比下降了-…

2023,简历石沉大海?软件测试岗位真的已经饱和了....

各大互联网公司的接连裁员&#xff0c;政策限制的行业接连消失&#xff0c;让今年的求职雪上加霜&#xff0c;想躺平却没有资本&#xff0c;还有人说软件测试岗位饱和了&#xff0c;对此很多求职者深信不疑&#xff0c;因为投出去的简历回复的越来越少了。 另一面企业招人真的…

执行事务合伙人和法人区别是什么

1. 定义不同&#xff1a; 执行事务合伙人指负责经营和管理合伙企业的人&#xff0c;对外代表合伙企业进行业务活动&#xff0c;对内负责合伙企业的日常管理。 法人则是企业的法定代表人&#xff0c;代表企业参与民事活动&#xff0c;是企业的行政领导&#xff0c;对企业经济活动…

MAT查找类(岔路口)-技巧

文章目录 前言一、现状二、使用步骤1.导出 hprof2.用MAT打开3.细节操作找大对象的线程名称查看线程的详情查找类的GC Roots柳暗花明检验真理 总结 前言 又是java 内存溢出 OOM JAVA MAT 分析工具大大的好。 高效查找问题根源&#xff0c;才是硬道理。 一、现状 mat 打开hprof…

CVE-2017-7529 Nginx越界读取内存漏洞

漏洞概述 当使用Nginx标准模块时&#xff0c;攻击者可以通过发送包含恶意构造range域的header请求&#xff0c;来获取响应中的缓存文件头部信息。在某些配置中&#xff0c;缓存文件头可能包含后端服务器的IP地址或其它敏感信息&#xff0c;从而导致信息泄露。 影响版本 Ngin…

vue3后台管理框架之技术栈

vue3全家桶技术 基础构建&#xff1a; vue3vite4TypeScript 代码格式 &#xff1a; eslintprettystylelint git生命周期钩子&#xff1a; husky css预处理器&#xff1a; sass ui库&#xff1a; element-plus 模拟数据: mock 网络请求&#xff1a; axios 路由&#xff1a; vue…

Three.js图案溶解shader

上图提供两种方式溶解显示 上面一排是根据现实的图案红色通道也就是r值进行溶解 下面一排提供额外的溶解纹理 可以通过简单更改呈现多种溶解图案 代码仓库 gitee b站账号&#xff1a;https://space.bilibili.com/374230437 interface IMapPath {map: string;dissolve?: string…

基于antd实现动态修改节点的Tree组件

前言 之前遇到一个需求&#xff0c;可对于任意节点添加或删除子节点。首先技术栈是基于reactant design&#xff0c;ant提供了Tree组件&#xff0c;但都是根据固定的数据渲染出树结构&#xff0c;如果需要新增或删除节点&#xff0c;官网并未提供。 实现过程 新增节点 首先…