在之前开发的混合App中H5使用vue+vant,针对移动端返回键对其中个别页面做了缓存处理,就用到了vue的内置组件 keep-alive ,今天总结一下该组件的使用及源码解析。
1.项目中的使用
话不多说直接上代码;
- App.js中代码
在App.js中使用keep-alive,配合router-view,将需要缓存的路由组件渲染在kepp-alive中,这里就需要配置路由时,设置路由对象的meta属性。<template> <div id="app"> <keep-alive> <router-view v-if="$route.meta.keepAlive" :key="$route.fullPath"></router-view> </keep-alive> <router-view v-if="!$route.meta.keepAlive" :key="$route.fullPath"></router-view> </div> </template> - router>index.js中代码
如下属性对象meta中keepAlive属性为true既表示可被缓存
{ path: '/refundOrderInfo', name: refundOrderInfo, component: refundOrderInfo, meta: { keepAlive: true } }, { path: '/propertyNotice', name: 'propertyNotice', component: propertyNotice } - 其他方式
在具体业务场景中还可以通过路由守卫函数动态设置meta.keepAlive的值,例如:
beforeRouteLeave (to, from, next) { if (xxx) { to.meta.keepAlive = true } next() }
2.源码解析
- keep-alive为内置组件,源码文件目录为src/core/components/keep-alive.js,接下来我会以源码内注释的形式解析,有描述错误的地方还望指正
先看export default的内容export default { name: 'keep-alive', abstract: true, // 表示此组件为抽象组件,抽象组件没有真实的节点,在组件渲染的时候不会解析渲染成真实的dom节点,而只是作为中间的数据过度层处理 props: { // include 表示只有匹配的组件会被缓存,而 exclude 表示任何匹配的组件都不会被缓存,props 还定义了 max,原以为它表示缓存的大小,再后面发现this.max会与this.keys.length作比较,大胆猜测max表示的是可允许被缓存组件个数的最大值 include: patternTypes, // patternTypes: Array<Function> = [String, RegExp, Array],支持字符串,正则表达式,数组 exclude: patternTypes, max: [String, Number] }, created () { this.cache = Object.create(null) // 以键值对形式建立缓存数据对象,key为组件唯一标识,value为组件对应的vnode, this.keys = [] // 缓存数据的keys集合 }, // 抽象组件销毁时触发 destroyed () { for (const key in this.cache) { pruneCacheEntry(this.cache, key, this.keys) /* 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) } */ // 由于此处未传第4个参数,已缓存的数据将会全部重置为null,并将key值从keys集合中移除 } }, mounted () { // 监听include和exclude,及时对需要缓存的组件集合动态计算 // match方法针对不同数据类型的的include,exclude做匹配判断 this.$watch('include', val => { pruneCache(this, name => matches(val, name)) }) this.$watch('exclude', val => { pruneCache(this, name => !matches(val, name)) }) /* function pruneCache (keepAliveInstance: any, filter: Function) { // keepAliveInstance既当前组件实例,_vnode是当前组件对应的虚拟dom树结构 const { cache, keys, _vnode } = keepAliveInstance for (const key in cache) { const cachedNode: ?VNode = cache[key] if (cachedNode) { const name: ?string = getComponentName(cachedNode.componentOptions) // 获取子组件name,如果子组件有name属性则取name值,如果没有则取使用时的标签名 if (name && !filter(name)) { // 如果未匹配中,将会重新遍历cache,删除不该再被缓存的组件数据 pruneCacheEntry(cache, key, keys, _vnode) } } } } */ }, ... 最后keep-alive是直接使用render函数 render () { const slot = this.$slots.default // 取默认插槽 const vnode: VNode = getFirstComponentChild(slot) // 获取默认的第一个子节点 // <keep-alive> 只处理第一个子元素,所以一般和它搭配使用的有 component 动态组件或者是 router-view const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { // check pattern const name: ?string = getComponentName(componentOptions) const { include, exclude } = this // 组件名如果满足了配置 include 且不匹配或者是配置了 exclude 且匹配,那么就直接返回这个组件的 vnode if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode } const { cache, keys } = this 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]) { // 如果cache[key]存在, 直接从缓存中拿vnode的组件实例 vnode.componentInstance = cache[key].componentInstance // make current key freshest // 由于key可能是在上述重新定义了,所以需要更新keys集合中key值 remove(keys, key) keys.push(key) } else { 若cache[key]不存在,设置缓存,将当前key放入keys集合中 cache[key] = vnode keys.push(key) // prune oldest entry // 最后的逻辑,判断缓存的个数是否超过max设置值,如果超出,将oldest key移除,由此可见keys集合是按先进先出队列结构维护的 if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } } // 此处为何要这样设置,待我了解了,后续更新 vnode.data.keepAlive = true return vnode || (slot && slot[0]) }