keep-alive的应用和底层实现原理的探索

devtools/2024/10/15 5:48:14/

一、概念

  • keep-alive 是 Vue.js 中的一个内置组件,它用于缓存组件的状态或避免对组件进行多次销毁和重建。通过使用 keep-alive 组件,可以在组件切换时将状态保留在内存中,以便在下次需要时直接复用,从而提高性能并改善用户体验。

  • 具体来说,当一个被 keep-alive 包裹的组件被切换出去时,它的状态会被保留,而不会被销毁。当再次切换回来时,该组件的状态会被恢复,避免了重新渲染和重新初始化。这对于包含大量动态数据、复杂计算或需要长时间初始化的组件来说,可以显著减少页面加载时间和提升用户体验。

  • 在 Vue.js 中使用 keep-alive 组件通常需要配合动态组件()或者路由来实现组件的切换和缓存。在实际开发中,常见的应用场景包括标签页切换、路由切换、模态框等需要保持状态的场景。

二、使用场景

javascript"><template><keep-alive :include="whiteList" :exclude="blackList" :max="count"><component :is="component"></component></keep-alive>
</template>

在路由中使用 keep-alive

javascript"><template><!-- vue2写法 --><keep-alive :include="whiteList" :exclude="blackList" :max="count"><router-view></router-view></keep-alive><!-- vue3写法 --><router-view v-slot="{ Component }"><keep-alive :include="whiteList" :exclude="blackList" :max="count"><component :is="Component" /></keep-alive></router-view>
</template>

也可以通过 meta 属性指定哪些页面需要缓存,哪些不需要

javascript"><template><!-- vue2写法 --><keep-alive><!-- 需要缓存的视图组件 --><router-view v-if="$route.meta.keepAlive"></router-view></keep-alive><!-- 不需要缓存的视图组件 --><router-view v-if="!$route.meta.keepAlive"></router-view><!-- vue3写法 --><router-view v-slot="{ Component }"><transition><keep-alive><!-- 需要缓存的视图组件 --><component :is="Component" v-if="route.meta.keepAlive" /></keep-alive><!-- 不需要缓存的视图组件 --><component :is="Component" v-if="!route.meta.keepalive" /></transition></router-view>
</template>

四、底层原理的实现

  • vue2 底层原理的实现
javascript">export default {name: 'keep-alive',abstract: true, // 防止组件实例被创建props: {include: patternTypes, // 白名单,指定哪些组件需要缓存exclude: patternTypes, // 黑名单,指定哪些组件不需要缓存max: [String, Number] // 缓存的最大个数},created () {this.cache = Object.create(null) // 缓存组件实例的对象this.keys = []  // 缓存的组件实例的键列表},destroyed () {for (const key in this.cache) { // keep-alive组件销毁时,删除所有缓存的组件实例pruneCacheEntry(this.cache, key, this.keys)}},mounted () { // 监控include和exclude属性的变化,根据变化来对缓存进行调整this.$watch('include', val => {pruneCache(this, name => matches(val, name))})this.$watch('exclude', val => {pruneCache(this, name => !matches(val, name))})},render () {const slot = this.$slots.defaultconst vnode = getFirstComponentChild(slot) // 获取slot中的第一个组件节点const componentOptions = vnode && vnode.componentOptions // 获取组件的选项if (componentOptions) {const name = getComponentName(componentOptions) // 获取组件名const { include, exclude } = thisif (// 不在白名单中(include && (!name || !matches(include, name))) ||// 在黑名单中(exclude && name && matches(exclude, name))) {return vnode // 不需要缓存的组件直接返回}const { cache, keys } = thisconst key = vnode.key == null? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') // 生成缓存的键: vnode.keyif (cache[key]) { // 如果有缓存,直接复用组件实例vnode.componentInstance = cache[key].componentInstanceremove(keys, key)keys.push(key) // LRU算法,最近使用的组件放在数组末尾} else {cache[key] = vnode // 缓存组件实例keys.push(key)if (this.max && keys.length > parseInt(this.max)) { // 如果超出最大缓存数,删除最久未使用的组件实例pruneCacheEntry(cache, keys[0], keys, this._vnode)}}vnode.data.keepAlive = true // 在组件VNode的data中增加keep-alive属性,表示该组件已被缓存}return vnode || (slot && slot[0]) // 返回处理后的VNode或原始的slot内容}
}
  • vue3 底层原理的实现
javascript">const KeepAliveImpl: ComponentOptions = {name: `KeepAlive`,__isKeepAlive: true,props: {include: [String, RegExp, Array], // 指定需要缓存的组件名称规则exclude: [String, RegExp, Array], // 指定不需要缓存的组件名称规则max: [String, Number]  // 缓存的最大组件实例数量},setup(props: KeepAliveProps, { slots }: SetupContext) {const instance = getCurrentInstance()! // 获取当前组件实例const sharedContext = instance.ctx as KeepAliveContext // 获取共享上下文// 创建缓存组件实例的 Map 和键集合const cache: Cache = new Map()const keys: Keys = new Set()let current: VNode | null = nullconst parentSuspense = instance.suspenseconst {renderer: {p: patch,m: move,um: _unmount,o: { createElement }}} = sharedContextconst storageContainer = createElement('div') // 创建一个用于存储组件对应DOM的容器// 激活组件时调用的方法sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {const instance = vnode.component!move(vnode, container, anchor, MoveType.ENTER, parentSuspense)// ... 省略部分代码}// 失活组件时调用的方法sharedContext.deactivate = (vnode: VNode) => {const instance = vnode.component!move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)// ... 省略部分代码}// 卸载组件function unmount(vnode: VNode) {// 重置 shapeFlag 以便能够正确卸载resetShapeFlag(vnode)_unmount(vnode, instance, parentSuspense, true)}// 处理缓存function pruneCache(filter?: (name: string) => boolean) {cache.forEach((vnode, key) => {const name = getComponentName(vnode.type as ConcreteComponent)if (name && (!filter || !filter(name))) {pruneCacheEntry(key)}})}// 移除头部缓存function pruneCacheEntry(key: CacheKey) {const cached = cache.get(key) as VNodeif (!current || !isSameVNodeType(cached, current)) {unmount(cached)} else if (current) {resetShapeFlag(current)}cache.delete(key)keys.delete(key)}// 监听 include 和 exclude 属性的变化,然后清理缓存watch(() => [props.include, props.exclude],([include, exclude]) => {include && pruneCache(name => matches(include, name))exclude && pruneCache(name => !matches(exclude, name))},// 在 `current` 更新后进行清理{ flush: 'post', deep: true })// 在渲染后缓存子树let pendingCacheKey: CacheKey | null = nullconst cacheSubtree = () => {if (pendingCacheKey != null) {cache.set(pendingCacheKey, getInnerChild(instance.subTree))}}onMounted(cacheSubtree)onUpdated(cacheSubtree)// ... 省略部分代码return () => {// ... 省略部分代码if (cachedVNode) {vnode.el = cachedVNode.el // 复用缓存的 DOMvnode.component = cachedVNode.componentif (vnode.transition) {// 递归更新子树上的过渡钩子setTransitionHooks(vnode, vnode.transition!)}// 避免 vnode 被作为新的组件实例被挂载vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE// LRU算法keys.delete(key)keys.add(key)} else {keys.add(key)// 移除最旧的缓存if (max && keys.size > parseInt(max as string, 10)) {pruneCacheEntry(keys.values().next().value)}}// 避免组件被卸载vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVEcurrent = vnodereturn isSuspense(rawVNode.type) ? rawVNode : vnode}}
}

小结: 核心原理就是缓存 + LRU 算法

五、keep-alive 中数据更新问题

beforeRouteEnter: 在有 vue-router 的项目,每次进入路由的时候,都会执行beforeRouteEnter

javascript">beforeRouteEnter(to, from, next){next(vm=>{vm.getData()  // 获取数据})
},

actived: 在keep-alive缓存的组件被激活的时候,都会执行actived钩子

javascript">activated(){this.getData() // 获取数据
},

http://www.ppmy.cn/devtools/44937.html

相关文章

【Vue】v-for中的key

文章目录 一、引入问题二、分析问题 一、引入问题 语法&#xff1a; key属性 "唯一值" 作用&#xff1a;给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用。 为什么加key&#xff1a;Vue 的默认行为会尝试原地修改元素&#xff08;就地复用&#xff09;…

容器多机部署eureka及相关集群服务出现 Request execution failed with message: AuthScheme is null

预期部署方案&#xff1a;两个eureka三个相关应用 注册时应用出现&#xff1a;Request execution failed with message: Cannot invoke “Object.getClass()” because “authScheme” is null&#xff0c;一开始认为未正确传递eureka配置的账户密码&#xff0c;例&#xff1a;…

视觉盛宴:探索大屏UI设计的卓越美学

视觉盛宴&#xff1a;探索大屏UI设计的卓越美学 在数字时代&#xff0c;用户界面&#xff08;UI&#xff09;设计不仅仅是功能性的体现&#xff0c;更是美学与技术融合的艺术。大屏UI设计&#xff0c;以其独特的视觉冲击力和交互体验&#xff0c;为用户带来了前所未有的视觉盛…

【Leetcode每日一题】 综合练习 - 组合(难度⭐⭐)(78)

1. 题目解析 题目链接&#xff1a;77. 组合 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 题目要求我们从 1 到 n 的整数集合中选择 k 个数的所有组合&#xff0c;且组合中的元素不考虑顺序。这意味着集合 [1, 2] 和…

ubuntu 22.04 appearance设置没有dock选项

1、问题描述 解决办法可以直接跳到后面见2 下图是我同学电脑的appearance界面选项&#xff0c;她有Dock的界面显示。 下面是我的界面&#xff0c; 没有Dock&#xff1a; 然后各种app的界面都在最底下&#xff0c;而且每次只能点击左上角的activities才能显示。 但是如果不打开某…

【wiki知识库】02.wiki知识库SpringBoot后端的准备

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;今日目标 二、&#x1f4c2;打开SpringBoot项目 2.1 导入所需依赖 2.2修改application.yml配置文件 2.3导入MybatisPlus逆向工程工具 2.4创建一个公用的返回值 2.5创建CopyUtil工具类 2.6创建…

学习 SSH Key 生成方法

SSH Key 是用于身份验证的一对密钥&#xff0c;包括公钥和私钥。公钥可以放在需要访问的服务器上&#xff0c;私钥则保留在本地。当你使用SSH连接到支持SSH Key认证的服务器时&#xff0c;服务器会用公钥来加密一个随机生成的字符串发送给客户端&#xff0c;客户端用私钥解密并…

安卓获取内部存储信息

目录 前言获取存储容量 前言 原生系统设置里的存储容量到底是怎么计算的&#xff0c;跟踪源码&#xff0c;涉及到VolumeInfo、StorageManagerVolumeProvider、PrivateStorageInfo、StorageStatsManager......等等&#xff0c;java上层没有办法使用简单的api获取到吗&#xff1f…