跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

关于对keep-alive的理解,使用场景以及存在的问题解读

目录结论先行

一、背景

二、含义

三、配置属性

① include:包含② exclude:排除,优先级大于 include③ max:定义组件的最大缓存个数

四、使用 keep-alive 的钩子函数执行顺序问题

五、应用场景

六、原理

七、keep-alive存在的问题:缓存后如何获取数据

八、总结

结论先行是 Vue 的内置组件,主要是用于缓存组件的实例,避免组件重复的被创建和销毁组件,提高应用的响应速度和性能。

原理:keep-alive 是一个缓存,会标记这个虚拟节点被缓存过了,后续就不会重新初始化,也不会进行销毁。

那常见的配置属性有 include 、exclude 和 max;include 和 exclude 用于指定需要缓存或排除的组件名称;

而 max 是用来定义组件的最大缓存个数。

内部采用 LRU 算法用于维护缓存列表。如果缓存个数超过最大数,那么会将最久没有被访问到的组件移出缓存列表,也就是销毁组件。

keep-alive 提供了两个钩子函数:activated钩子和deactivated钩子。

activated:

当组件被激活(使用)的时候触发,常用于执行一些数据初始化或者异步操作;

deactivated:

当组件失活(不被使用)的时候触发;

但是在使用 keep-alive 时,要注意数据的更新问题,避免出现数据无法更新的情况。

一、背景

在平常开发中,有部分组件没有必要多次初始化。这时,我们需要将组件进行持久化,使组件的状态维持不变。

在下一次展示时,也不会进行重新初始化组件。keep-alive就有这个功能。

二、含义

是 Vue 的内置组件,可以使被包裹的组件保留状态,避免重复的创建和销毁组件,提高应用的响应速度和性能。

当一个组件被标签包裹时,会缓存组件的实例在内存中,而不会把组件销毁。当这个组件再次被使用时,Vue会从缓存中提取组件的实例,将其重新挂载到页面上。

这个功能可以提高应用的性能,特别是在需要频繁切换组件的场景下,就比如Tab切换或者路由切换,因为不需要每一次切换时都重新创建和销毁组件,而是直接从缓存中获取,这样可以避免重复的初始化和渲染,从而提高应用的响应速度和性能。

举个应用场景:

有个员工列表,现在我们点击某条数据,查看员工详情后,再返回到员工列表。这个时候我们就希望这个列表能够保持刚才的状态,这时候就可以使用keep-alive把这个列表所在的组件包裹。

和 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。

三、配置属性

include 和 exclude用于指定需要缓存或排除的组件名称;

max 和 min 用于指定缓存的最大和最小数量。

① include:包含值为字符串 / 正则表达式 / 数组。只有组件的名称(name)与 include 的值相同的才会被缓存。即指定哪些被缓存,可以指定多个被缓存。

这里以字符串为例,指定多个组件缓存,语法是用逗号隔开。

如下:

// 指定 home 组件和 about 组件被缓存② exclude:排除,优先级大于 include同上,指定哪些组件不被缓存。

// 除了home组件和about组件,别的都缓存使用 include 和 exclude 属性时,缓存组件的名称 name 一定要赋值,否则无法识别到对应的路由组件。

如果name选项不可用,则匹配它的局部注册名称 (父组件components选项的键值),匿名组件不能被匹配。

当使用正则或者是数组时,要记得使用v-bind 。

③ max:定义组件的最大缓存个数内部采用LRU算法用于维护缓存列表。如果缓存个数超过最大数,那么会将最久没有被访问到的组件移出缓存列表,也就是销毁组件。

四、使用 keep-alive 的钩子函数执行顺序问题

keep-alive 提供了两个钩子函数:activated钩子和deactivated钩子。

activated:

当组件被激活(使用)的时候触发,即进入这个页面的时候触发,常用于执行一些数据初始化或者异步操作;

deactivated:

当组件失活(不被使用)的时候触发,即离开这个页面的时候触发;

① 首次进出组件时:beforeRouteEnter>beforeCreate>created>mounted>activated> ... ... >beforeRouteLeave>deactivated② 再次进出组件时:beforeRouteEnter>activated> ... ... >beforeRouteLeave>deactivated③ 结论初始进入和离开 created ---> mounted ---> activated --> deactivated后续进入和离开 activated --> deactivated

五、应用场景

① 缓存动态组件

② 缓存路由

Vue2写法:

Vue3写法:

在缓存路由的时候,需要借助 v-slot 插槽 + 动态组件的方式,实现路由的缓存。

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

③ 实际场景1)查看表格某条数据的详情页,返回还是之前的状态。比如还是之前的筛选结果,还是之前的页数等;

2)填写的表单的内容路由跳转返回还在。比如 input框、下选择拉框、开关切换等用户输入了一大把东西,跳转再回来不能清空啊,不用让用户再写一遍。

六、原理

keep-alive 是一个缓存,会标记这个虚拟节点被缓存过了。

后续就不会重新初始化,也不会进行销毁。

keep-alive是Vue中内置的一个组件,源码位置:src/core/components/keep-alive.js

解析:

abstract:true,标记这个组件没有任何含义,不需要记录父子关系中。

export default {name: 'keep-alive',abstract: true,

props: {include: [String, RegExp, Array],exclude: [String, RegExp, Array],max: [String, Number]},

created () {this.cache = Object.create(null) this.keys = []},

destroyed () {for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)}},

mounted () {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.default const vnode = getFirstComponentChild(slot)/* 获取该组件节点的componentOptions */ const componentOptions = vnode && vnode.componentOptions

if (componentOptions) {/* 获取该组件节点的名称,优先获取组件的name字段,如果name不存在则获取组件的tag */ const name = getComponentName(componentOptions)

const { include, exclude } = this/* 如果name不在inlcude中或者存在于exlude中则表示不缓存,直接返回vnode */ if ((include && (!name || !matches(include, name))) ||// excluded (exclude && name && matches(exclude, name))) {return vnode}

const { cache, keys } = this/* 获取组件的key值 */ const key = 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/* 拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存 */ if (cache[key]) {vnode.componentInstance = cache[key].componentInstance// make current key freshest remove(keys, key) keys.push(key)}/* 如果没有命中缓存,则将其设置进缓存 */ else {cache[key] = vnode keys.push(key)// prune oldest entry/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */ if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)}}

vnode.data.keepAlive = true}return vnode || (slot && slot[0])}}可以看到该组件没有template,而是用了render,在组件渲染的时候会自动执行render函数this.cache是一个对象,用来存储需要缓存的组件,它将以如下形式存储:this.cache = {'key1':'组件1','key2':'组件2',// ...

}在组件销毁的时候执行pruneCacheEntry

函数:

function pruneCacheEntry (cache: VNodeCache,key: string,keys: Array,current?: VNode) {const cached = cache[key]/* 判断当前没有处于被渲染状态的组件,将其销毁*/ if (cached && (!current || cached.tag !== current.tag)) {cached.componentInstance.$destroy()}cache[key] = null remove(keys, key)}在mounted钩子函数中观测include和exclude

的变化,如下:

mounted () {this.$watch('include', val => {pruneCache(this, name => matches(val, name))}) this.$watch('exclude', val => {pruneCache(this, name => !matches(val, name))})}如果include或exclude发生了变化,即表示定义需要缓存的组件的规则或者不需要缓存的组件的规则发生了变化,那么就执行pruneCache

函数,函数如下:

function pruneCache (keepAliveInstance, filter) {const { cache, keys, _vnode } = keepAliveInstance for (const key in cache) {const cachedNode = cache[key]if (cachedNode) {const name = getComponentName(cachedNode.componentOptions) if (name && !filter(name)) {pruneCacheEntry(cache, key, keys, _vnode)}}}}在该函数内对this.cache对象进行遍历,取出每一项的name值,用其与新的缓存规则进行匹配,如果匹配不上,则表示在新的缓存规则下该组件已经不需要被缓存,则调用pruneCacheEntry函数将其从this.cache对象剔除即可。

关于keep-alive的最强大缓存功能是在render函数中实现,首先获取组件的key

值:

const key = vnode.key == null?componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : ''): vnode.key拿到key值后去this.cache对象中去寻找是否有该值,如果有则表示该组件有缓存,即命中缓存,如下:/* 如果命中缓存,则直接从缓存中拿 vnode 的组件实例 */ if (cache[key]) {vnode.componentInstance = cache[key].componentInstance/* 调整该组件key的顺序,将其从原来的地方删掉并重新放在最后一个 */ remove(keys, key) keys.push(key)} 直接从缓存中拿vnode的组件实例,此时重新调整该组件key的顺序,将其从原来的地方删掉并重新放在this.keys中最后一个。

this.cache对象中没有该key

值的情况,如下:

/* 如果没有命中缓存,则将其设置进缓存 */ else {cache[key] = vnode keys.push(key)/* 如果配置了max并且缓存的长度超过了this.max,则从缓存中删除第一个 */ if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)}}表明该组件还没有被缓存过,则以该组件的key为键,组件vnode为值,将其存入this.cache中,并且把key存入this.keys中。

此时再判断this.keys中缓存组件的数量是否超过了设置的最大缓存数量值this.max,如果超过了,则把第一个缓存组件删掉。

七、keep-alive存在的问题:缓存后如何获取数据

存在的问题:

数据更新问题,缓存的组件重新进入不会再触发created生命周期中的方法,因此数据不会更新。

解决办法:

① 在 beforeRouteEnter 钩子函数中,在路由进入之前先获取数据:每次组件渲染或者每次进入路由的时候,都会执行beforeRouteEnter。

beforeRouteEnter(to, from, next){next(vm=>{console.log(vm)// 每次进入路由执行vm.getData() // 获取数据})},② 在 activated 生命周期中,获取数据:在keep-alive缓存的组件被激活的时候,都会执行actived钩子。

activated() {this.getData() // 获取数据},注意:服务器端渲染期间activated不被调用。

八、总结

总的来说,使用 keep-alive可以有效地提高应用的响应速度和性能,特别是在需要频繁切换组件的情况下。

但是在使用 keep-alive 时,要注意数据的更新问题,避免出现数据无法更新的情况。

keep-alive 是一个缓存,会标记这个虚拟节点被缓存过了,后续就不会重新初始化,也不会进行销毁。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:vue中的keep-alive详解与应用场景Vue3除了keep-alive还有哪些实现页面缓存详解Vue3手动清理keep-alive组件缓存的方法详解Vue Router 配合 keep-alive 不生效的问题及解决方案浅析对Vue中keep-alive缓存组件的理解

相关文章