面试官:说说你对keep-alive的理解是什么?怎么缓存当前的组件?缓存后怎么更新?
故心故心故心故心小故冲啊
文章目录
- 一、Keep-alive 是什么
- 二、使用场景
- 三、原理分析
- 四、思考题:缓存后如何获取数据
- 参考文献
一、Keep-alive 是什么
keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM
keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
keep-alive可以设置以下props属性:
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
- max - 数字。最多可以缓存多少组件实例
关于keep-alive的基本用法:
<keep-alive><component :is="view"></component>
</keep-alive>
使用includes和exclude:
<keep-alive include="a,b"><component :is="view"></component>
</keep-alive><!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/"><component :is="view"></component>
</keep-alive><!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']"><component :is="view"></component>
</keep-alive>
匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配
设置了 keep-alive 缓存的组件,会多出两个生命周期钩子(activated与deactivated):
- 首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > … … > beforeRouteLeave > deactivated
- 再次进入组件时:beforeRouteEnter >activated > … … > beforeRouteLeave > deactivated
二、使用场景
使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive
举个栗子:
当我们从首页–>列表页–>商详页–>再返回,这时候列表页应该是需要keep-alive
从首页–>列表页–>商详页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive
在路由中设置keepAlive属性判断是否需要缓存
{path: 'list',name: 'itemList', // 列表页component (resolve) {require(['@/pages/item/list'], resolve)},meta: {keepAlive: true,title: '列表页'}
}
使用<keep-alive>
<div id="app" class='wrapper'><keep-alive><!-- 需要缓存的视图组件 --> <router-view v-if="$route.meta.keepAlive"></router-view></keep-alive><!-- 不需要缓存的视图组件 --><router-view v-if="!$route.meta.keepAlive"></router-view>
</div>
三、原理分析
keep-alive是vue中内置的一个组件
源码位置:src/core/components/keep-alive.js
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.defaultconst vnode = getFirstComponentChild(slot)/* 获取该组件节点的componentOptions */const componentOptions = vnode && vnode.componentOptionsif (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 freshestremove(keys, key)keys.push(key)}/* 如果没有命中缓存,则将其设置进缓存 */else {cache[key] = vnodekeys.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<string>,current?: VNode
) {const cached = cache[key]/* 判断当前没有处于被渲染状态的组件,将其销毁*/if (cached && (!current || cached.tag !== current.tag)) {cached.componentInstance.$destroy()}cache[key] = nullremove(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 } = keepAliveInstancefor (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] = vnodekeys.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,如果超过了,则把第一个缓存组件删掉
四、思考题:缓存后如何获取数据
解决方案可以有以下两种:
- beforeRouteEnter
- activated
beforeRouteEnter
每次组件渲染的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){next(vm=>{console.log(vm)// 每次进入路由执行vm.getData() // 获取数据})
}
**activated **
在keep-alive缓存的组件被激活的时候,都会执行actived钩子
activated(){this.getData() // 获取数据
},
注意:服务器端渲染期间avtived不被调用
参考文献
https://www.cnblogs.com/dhui/p/13589401.html
https://www.cnblogs.com/wangjiachen666/p/11497200.html
https://vue3js.cn/docs/zh
面试官:说说你对keep-alive的理解是什么?怎么缓存当前的组件?缓存后怎么更新?相关推荐
- 岗位理解_当面试官问求职者对应聘岗位的理解情况
虽然求职是看的是岗责是否匹配自己的经历和能力,但是很多面试官还是会问这样的问题.对于求职者来说,对岗位的理解其实就是把岗位职责背一遍,但是面试官既然问了,就要说出一些其他内容来.想回答好这个问题,就要 ...
- 面试官:给我说一下你理解的分布式架构
转载自 面试官:给我说一下你理解的分布式架构 # 什么是分布式架构 分布式系统(distributed system) 是建立在网络之上的软件系统. 内聚性:是指每一个数据库分布节点高度自治,有本地 ...
- 精选(26)面试官:讲讲你对ThreadLocal的理解
前言 在面试环节中,考察"ThreadLocal"也是面试官的家常便饭,所以对它理解透彻,是非常有必要的. 有些面试官会开门见山的提问: "知道ThreadLocal吗? ...
- “约见”面试官系列之常见面试题之第九十五篇之vue-router的组件组成(建议收藏)
<router-link :to='' class='active-class'> //路由声明式跳转 ,active-class是标签被点击时的样式<router-view> ...
- 面试官:领导让你开车送醉酒客户回家,你送吗?95后:傻子才送
这道面试题,确确实实是一道真题,而且非常有分量.前不久,某公司招聘,老板(领导)亲自参加二面,不知道出于什么目的,突然问了这样一个问题:你的老板(领导)请客户吃饭,结果同事或客户喝得烂醉.你因为开车没 ...
- 面试官:说说你对vue的理解?
一.web发展历史 Web是World Wide Web的简称,中文译为万维网 我们可以将它规划成如下的几个时代来进行理解 石器时代 文明时代 工业革命时代 百花齐放时代 石器时代 石器时代指的就是我 ...
- 原来腾讯面试题也不难,面试官:给我说一下你理解的分布式架构?
什么是分布式架构 分布式系统(distributed system) 是建立在网络之上的软件系统. 内聚性:是指每一个数据库分布节点高度自治,有本地的数据库管理系统. 透明性:是指每一个数据库分布节点 ...
- 面试官:谈谈你对零拷贝的理解~
点击上方 好好学java ,选择 星标 公众号重磅资讯,干货,第一时间送达 今日推荐:14 个 github 项目!个人原创100W +访问量博客:点击前往,查看更多 前言 从字面意思理解就是数据不需 ...
- 面试官:谈谈对JS闭包的理解及常见应用场景(闭包的作用)
文章目录 对JS闭包的理解及常见应用场景(闭包的作用) 1.变量作用域 2.如何从外部读取函数内部的变量? 3.闭包概念 4.闭包用途 5.闭包的理解 6.闭包应用场景 setTimeout传参 回调 ...
最新文章
- 展讯camera去除尺寸相关缓存
- Golang之函数选项模式
- BZOJ3211: 花神游历各国
- 设计模式学习笔记--Flyweight享元模式
- 3 款实用的在线JS代码工具推荐
- rffc2071_基于RFFC2071的变频器设计
- 软件需求分析——需求工程导论
- 关于文章手把手教你使用stata做竞争风险模型的一些其他操作
- Related Problems
- 一张图看懂企业经营,企业战略的本质,企业数字化转型 Road Map, 企业大脑的本质...
- 测试网上哪款软件最好,手机测试软件哪款好用?4款测试软件推荐
- 我是如何获取到全校同学的证件照?
- 高校实验室仪器设备管理系统/实验室管理系统
- 苹果和Android用点应用对比是真的吗,苹果手机与安卓手机相比,真的是苹果手机完胜吗...
- Retrying connect to server: xxx.xxx.xxx.xxx:8032. Already tried 0 time(s)
- Java/Kotlin 最多保留两位小数处理方法
- JD开放平台接口(获得JD商品详情, 按关键字搜索商品,按图搜索京东商品(拍立淘), 获得店铺的所有商品,获取推荐商品列表, 获取购买到的商品订单列表)
- 用opencv简单创建单色图片并保存
- Debian与Ubuntu到底有什么不同,应该如何选择?
- 科技爱好者周刊之爱好者记录