携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 30 天,点击查看活动详情
start
- 一直对 keep-alive 掌握的不熟练,今天学习一下
开始
官方解释:
基础使用
app.vue
<template>
<!-- keep-alive 的基础用法 -->
<div id="app">
<!-- <keep-alive>
<component :is="view"></component>
</keep-alive> -->
<keep-alive>
<HelloWorld v-if="view === 'HelloWorld'"> </HelloWorld>
<demo v-else />
</keep-alive>
<button @click="checkout">点我</button>
</div>
</template>
<script>
import HelloWorld from '@/components/HelloWorld.vue'
import demo from '@/components/demo.vue'
export default {
name: 'app',
data() {
return {
view: 'HelloWorld',
}
},
components: {
HelloWorld,
demo,
},
methods: {
checkout() {
if (this.view === 'demo') {
this.view = 'HelloWorld'
} else {
this.view = 'demo'
}
},
},
}
</script>
HelloWorld.vue
<template>
<div>我是helloworld</div>
</template>
<script>
export default {
created() {
// 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
console.log('helloworld组件---created')
},
activated() {
console.log('helloworld组件---activated')
},
deactivated() {
console.log('helloworld组件---deactivated')
},
}
</script>
<style></style>
demo.vue
<template>
<div>我是demo函数</div>
</template>
<script>
export default {
created() {
// 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
console.log('demo组件---created')
},
activated() {
console.log('demo组件---activated')
},
deactivated() {
console.log('demo组件---deactivated')
},
}
</script>
生命周期函数
代码:
<template>
<div>我是demo函数</div>
</template>
<script>
export default {
created() {
// 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
console.log('demo组件---created')
},
activated() {
console.log('demo组件---activated')
},
deactivated() {
console.log('demo组件---deactivated')
},
}
</script>
效果:
实践结果:
- 首次加载某个组件的时候,会执行该组件的 created 钩子,后续切换组件,不会触发组件的 created ;
- 使用
keep-alive
缓存的组件,来回切换的时候,激活的时候会触发activated
,取消激活的时候会触发deactivated
activate
英文释义:激活的。
其他参数
include
and exclude
2.1.0 新增
include
和 exclude
prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:
<!-- 逗号分隔字符串 -->
<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>
max
2.5.0 新增
最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
tips:
<keep-alive :include="['HelloWorld']">
<router-view></router-view>
</keep-alive>
// HelloWorld.vue
<template>
<div>我是helloworld</div>
</template>
<script>
export default {
name: 'HelloWorld', // 这里需要声明
created() {
// 首次加载的时候,会执行。但是因为被缓存了,后续切换组件不会执行;
console.log('helloworld组件---created')
},
activated() {
console.log('helloworld组件---activated')
},
deactivated() {
console.log('helloworld组件---deactivated')
},
}
</script>
// 使用路由懒加载的情况,页面需要添加 name 属性
总结
keep-alive
主要作用:缓存组件;- 支持参数:
include
,exclude
,max
; - 支持生命周期钩子:
activated
,取消激活的时候会触发deactivated
阅读源码
"vue": "^2.5.22"
node_modules\vue\src\core\components\keep-alive.js
/* @flow */
import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'
type VNodeCache = { [key: string]: ?VNode };
// 获得组件的名称
function getComponentName (opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag)
}
// 解析 include/exclude 的函数,包含则返回 true
function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
/* istanbul ignore next */
return false
}
// 减少缓存 (判断需不需要删除缓存的方法)
function pruneCache (keepAliveInstance: any, filter: Function) {
// keepAliveInstance => this => vm实例
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode: ?VNode = cache[key]
// 如果缓存中存在 该虚拟节点
if (cachedNode) {
// 获取组件的名称
const name: ?string = getComponentName(cachedNode.componentOptions)
// 存在名字,且 过滤函数为 false,删除该缓存 (例如 include不包含返回false,不包含则删除缓存)
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
// 减少缓存入口 (真正去删除缓存的方法)
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] = null
remove(keys, key)
}
// 2.1 数组正则字符串
const patternTypes: Array<Function> = [String, RegExp, Array]
export default {
// 1. 组件名
name: 'keep-alive',
// 1.1 是否是虚拟组件 // 判断此组件是否需要在渲染成真实DOM
abstract: true,
// 2. 组件属性
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
// 3. 创建了一个空对象用来存储缓存的虚拟dom
this.cache = Object.create(null)
// 3.1 创建一个数组 存储key
this.keys = []
},
destroyed () {
// 5. 组件销毁的时候
for (const key in this.cache) {
// 5.1 直接调用删除组件的方式
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
// 4. 监听 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.default
// 得到插槽中第一个组件的vnode
const vnode: VNode = getFirstComponentChild(slot)
// 得到组件的配置
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
// 获取组件名字
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
// 名称不在 include
(include && (!name || !matches(include, name))) ||
// excluded
// 名字在 exclude
(exclude && name && matches(exclude, name))
) {
// 直接返回虚拟节点
return vnode
}
const { cache, keys } = this
// 定义组件的key
const key: ?string = 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
// 如果存在缓存
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
// 保证当前key是最新的,删除历史的key,讲key放在数组最末尾
remove(keys, key)
keys.push(key)
} else {
// 如果不存在缓存,开始缓存
cache[key] = vnode
keys.push(key)
// prune oldest entry
// 如果配置了最大缓存数量,缓存数组长度大于最大缓存数量
if (this.max && keys.length > parseInt(this.max)) {
// 删除第一项
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
// shared/util
export function remove (arr: Array<any>, item: any): Array<any> | void {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
总结:
- 跟着注释的序号,依次进行阅读
keep-alive
本质是一个组件;- 组件内部缓存着 vnode。
- 自定义了一个render函数,结合插槽和缓存的 vnode完成了组件的缓存。
- 传入的参数
include
,exclude
,max,都是在render中使用的。