目录
对keep-alive的理解,它是如何实现的,具体缓存的是什么?
keep-alive 组件的作用是在组件切换时,保存一些组件的状态,防止多次渲染。它通过缓存组件的 vnode 实例来实现。
具体实现步骤如下:
- 创建一个缓存对象
cache
和一个存储组件 key 的数组keys
。 - 在组件的
created
钩子函数中初始化缓存对象和数组。 - 在组件的
destroyed
钩子函数中销毁缓存对象。 - 在组件的
mounted
钩子函数中监听include
和exclude
属性的变化,并根据最新的属性值实时削减缓存的组件。 - 在组件的
render
函数中,判断当前组件是否需要缓存。- 如果不需要缓存,则直接返回组件的 vnode 实例。
- 如果需要缓存,则获取组件的 key,并判断缓存对象中是否存在该 key。
- 如果存在,则将缓存对象中对应的组件实例赋给 vnode,并更新组件在数组中的位置(LRU 缓存策略)。
- 如果不存在,则将组件的 vnode 实例添加到缓存对象中,并将组件的 key 添加到数组中,并判断数组长度是否超过最大缓存数量,如果超过,则移除最早未使用的组件。
- 最后将组件的
keepAlive
属性设置为true
,表示该组件需要被缓存。
需要注意的是,keep-alive 只对第一个子组件有效,且只有在组件的 created
钩子函数中才会执行组件的初始化操作,后续的更新操作会通过 patch 过程直接插入缓存的 DOM 对象,不会再执行组件的 created
和 mounted
钩子函数。
使用 keep-alive 组件的示例:
<template>
<div>
<button @click="toggleComponent">Toggle Component</button>
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</div>
</template>
<script>
export default {
data() {
return {
currentComponent: 'ComponentA',
};
},
methods: {
toggleComponent() {
this.currentComponent = this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA';
},
},
components: {
ComponentA: {
template: '<div>Component A</div>',
},
ComponentB: {
template: '<div>Component B</div>',
},
},
};
</script>
在上述示例中,有两个组件 ComponentA 和 ComponentB。通过点击按钮,可以切换显示的组件。由于这两个组件被包裹在 keep-alive 组件中,所以在切换组件时,组件的状态会被保留,不会重新渲染。
当初始显示 ComponentA 时,点击按钮后会切换到 ComponentB。再次点击按钮,会切换回 ComponentA。在切换过程中,组件的状态会被保留,不会重新渲染。
这个示例展示了 keep-alive 组件的作用,它可以在组件切换时保存组件的状态,避免多次渲染,提高性能。
<keep-alive>
组件
<keep-alive>
是 Vue.js 中的一个抽象组件,用于缓存动态组件。当使用 <keep-alive>
包裹动态组件时,这些组件将会被缓存,而不是每次切换都重新渲染。
属性
<keep-alive>
有以下三个属性:
include
: 字符串或正则表达式,只有名称匹配的组件会被缓存。exclude
: 字符串或正则表达式,任何名称匹配的组件都不会被缓存。max
: 数字,最多可以缓存多少组件实例。
工作流程
- 当包裹动态组件时,
<keep-alive>
会监听动态组件的生命周期钩子。 - 当切换到一个新的动态组件时,如果该组件匹配
include
并且不匹配exclude
,它将会被缓存。 - 如果组件被缓存,它的状态将会被保留,包括已经触发的生命周期钩子和当前的数据状态。
- 当再次切换回已经被缓存的组件时,Vue.js 将会重新激活这个组件,并且不会再次创建它。
- 如果设置了
max
属性,当缓存组件数量超过max
值时,将会按照一定策略清除缓存中的组件实例。
通过这种方式,可以提高应用性能,因为在切换组件时,不需要每次都销毁和重新创建组件,而是直接从缓存中读取已存在的组件状态。
render 函数
render () {
//
function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
const slot = this.$slots.default // 获取默认插槽
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 && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
// 如果不匹配当前组件的名字和include以及exclude
// 那么直接返回组件的实例
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]) {
// LRU缓存策略执行
vnode.componentInstance = cache[key].componentInstance // 组件初次渲染的时候componentInstance为undefined
// make current key freshest
remove(keys, key)
keys.push(key)
// 根据LRU缓存策略执行,将key从原来的位置移除,然后将这个key值放到最后面
} else {
// 在缓存列表里面没有的话,则加入,同时判断当前加入之后,是否超过了max所设定的范围,如果是,则去除
// 使用时间间隔最长的一个
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 将组件的keepAlive属性设置为true
vnode.data.keepAlive = true // 作用:判断是否要执行组件的created、mounted生命周期函数
}
return vnode || (slot && slot[0])
}
这段代码是关于 Vue 中 keep-alive
组件的 render
函数的实现。它描述了 keep-alive
是如何通过缓存来存储组件的 vnode 实例,并根据一定的策略来管理这些缓存的过程。
具体而言,这段代码做了以下几件事情:
- 获取
keep-alive
下第一个子组件的实例对象,并获取该组件的组件名。 - 通过当前组件名匹配 include 和 exclude,判断当前组件是否需要缓存。如果不需要缓存,则直接返回当前组件的实例 vnode。
- 如果需要缓存,判断当前组件是否在缓存数组里:
- 如果存在,则将原位置上的 key 从数组中移除,同时将这个组件的 key 放到数组的最后面(LRU)。
- 如果不存在,则将组件的 key 放入数组。然后判断当前 key 数组是否超过了 max 所设置的范围,如果超过,则删除未使用时间最长的一个组件的 key。
- 最后,将这个组件的 keepAlive 属性设置为 true。
这个实现使得 keep-alive
能够根据一定的策略来管理组件的缓存,以提高页面性能和用户体验。
keep-alive 本身的创建过程和 patch 过程
首次渲染
● 组件的首次渲染∶判断组件的 abstract 属性,才往父组件里面挂载 DOM
// core/instance/lifecycle
function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) {
// 判断组件的abstract属性,才往父组件里面挂载DOM
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {
}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
判断当前 keepAlive 和 componentInstance 是否存在来判断是否要执行组件 prepatch 还是执行创建 componentlnstance
// core/vdom/create-component
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// componentInstance在初次是undefined!!!
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode) // prepatch函数执行的是组件更新的过程
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
这段描述涉及了 Vue 中 keep-alive
组件的创建和 patch 过程,以及在缓存渲染时如何处理组件的生命周期钩子函数。
首先,描述了首次渲染过程:
- 在组件的首次渲染中,会判断组件的
abstract
属性,只有当该属性为 false 时才会将 DOM 挂载到父组件中。 - 然后,在初始化阶段,根据当前的
keepAlive
和componentInstance
是否存在来判断是否要执行组件的prepatch
过程还是执行创建componentInstance
。如果componentInstance
在初次渲染时是 undefined,则会执行创建componentInstance
的操作。
接着,描述了缓存渲染时的处理过程:
- 当进行缓存渲染时,会根据
vnode.componentInstance
(首次渲染时为 undefined)和keepAlive
属性来判断是否执行组件的created
、mounted
等生命周期钩子函数。如果组件被缓存,那么不会执行这些生命周期钩子函数,而是直接对缓存的组件执行 patch 过程:将缓存的 DOM 对象直接插入到目标元素中,完成数据更新的渲染过程。
这个描述总结了 Vue 中 keep-alive
组件在首次渲染和缓存渲染时的行为,以及它是如何管理组件的生命周期钩子函数和 DOM 渲染的过程。
LRU (least recently used)缓存策略
LRU(Least Recently Used)缓存策略是基于数据的历史访问记录来淘汰数据的算法。其核心思想是,如果数据最近被访问过,那么将来被访问的几率也更高。
常见的实现方式是使用一个链表保存缓存数据,并按照以下步骤进行操作:
- 当新数据插入时,将其插入到链表的头部。
- 每当缓存命中(即缓存数据被访问),就将对应的数据移到链表的头部。
- 当链表已满时,将链表尾部的数据丢弃,以便为新数据腾出空间。
通过这种方式,LRU 算法能够有效管理缓存数据,保留最常用的数据,并在需要时清理不常用的数据,以提高缓存的命中率和性能。
总结:keep-alive 组件通过缓存组件的 vnode 实例来保存组件的状态,实现了组件切换时的状态保持。它的实现原理是使用缓存对象和数组来存储组件实例,并根据缓存策略(LRU)来管理缓存的组件数量。
持续学习总结记录中,回顾一下上面的内容:
对 keep-alive 的理解:
<keep-alive>
是 Vue 提供的一个抽象组件,用于在组件之间切换时缓存组件的状态或 DOM。当组件被切换出去时,会被<keep-alive>
缓存起来,而不是被销毁,这样可以提高性能和用户体验。
它是如何实现的:
<keep-alive>
利用了 Vue 的抽象组件能力,在内部维护一个缓存对象,用于存储被缓存组件的状态和 DOM 结构。当需要缓存的组件被激活时,从缓存对象中检索相应的组件,如果存在,则直接复用之前的状态和 DOM,而不需要重新创建。
具体缓存的是什么:
<keep-alive>
缓存的是组件的实例及其状态、数据以及渲染出的 DOM 结构。这样,在组件被再次激活时,可以直接从缓存中获取到这些信息,而不需要重新初始化组件及其状态,从而提高了性能。