组件介绍:
要想搞明白组件的内部实现原理,首先我们得搞明白这个组件怎么用以及为什么要用它,关于组件,如下介绍:
简单来说:就是我们可以把一些不常变动的组件或者需要缓存的组件用包裹起来,这样就会帮我们把组件保存在内存中,而不是直接的销毁,这样做可以保留组件的状态或避免多次重新渲染,以提高页面性能。
用法
- include 和 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 选项的键值),也就是组件的标签值。匿名组件不能被匹配。
- max表示最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
实现原理
是 Vue 源码中实现的一个组件,也就是说 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.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
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
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
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
缓存整个项目
在 App.vue 里面
如果需要缓存整个项目,则如下设置(直接包裹根router-view即可):
<template>
<div id="app">
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>使用keepAlive后生命周期发生了哪些变化
<script>
export default {
name: 'App'
}
</script>
结合Router,缓存部分页面
1、在App.vue中设置:(根据是否需要缓存切换模式)
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
2、在router.js路由页设置:
new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Home,
redirect: 'goods',
children: [
{
path: 'goods',
name: 'goods',
component: Goods,
meta: {
keepAlive: false // 不需要缓存
}
},
{
path: 'ratings',
name: 'ratings',
component: Ratings,
meta: {
keepAlive: true // 需要缓存
}
},
{
path: 'seller',
name: 'seller',
component: Seller,
meta: {
keepAlive: true // 需要缓存
}
}
]
}
]
})
注意:配置了keepAlive的页面,在再次进入时不会重新渲染,该页面内的组件同理不会再次渲染。而这可能会导致该组件内的相关操作(那些每次都需要重新渲染页面的操作:如父子组件间的传值)不再生效。 这一点可能会导致一些莫名其妙而又无从查证的bug
使用新增属性include/exclude,缓存部分页面
vue2.1.0 新增了include,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 选项的键值)。匿名组件不能被匹配。
动态判断,使用v-bind:include
<keep-alive :include="includedComponents">
<router-view></router-view>
</keep-alive>
includedComponents动态设置即可
使用beforeRouteLeave或者afterEach中进行拦截处理
如在项目在Category组件中的设置:
beforeRouteLeave(to,from,next){
if(to.name=='DemoIndex'){
if(!from.meta.keepAlive){
from.meta.keepAlive=true
}
next()
}else{
from.meta.keepAlive=false
to.meta.keepAlive=false
next()
}
},
在beforeRouteLeave中to.name根据具体的路由进行动态缓存设置。
列表页回到上次浏览位置
条件:1、列表页不可使用懒加载延迟加载数据,2、列表页需要使用keepAlive缓存
beforeRouteLeave(to,from,next){ //离开页面之前将高度存储到sessionStorage。这里不建议用localStorage,因为session在关闭浏览器时会自动清除,而local则需要手动清除,有点麻烦。
sessionStorage.setItem('scrollH',document.getElementById('demo').scrollTop)
next()
},
activated(){ //在activated生命周期内,从sessionStorage中读取高度值并设置到dom
if(sessionStorage.getItem('scrollH')){
document.getElementById('demo').scrollTop=sessionStorage.getItem('scrollH')
}
},