一、KeepAlive是什么
keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM
keep-alive 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
keep-alive 保持组件活跃,不会被destroy销毁掉,就一直还活着,组件没有被销毁掉的话,组件上挂载的数据就还存在,所以状态就可以保留,所以,keep-alive就可以保持组件的状态。
http协议的请求头里面也有一个keep-alive,保持http通话,这样:Connection: keep-alive 功能虽然不一样,但是思想上是一样的即为~保持状态活跃
1.KeepAlive的props属性
prop 的值都可以是一个以英文逗号分隔的字符串、一个正则表达式,或是包含这两种类型的一个数组。
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存
<!-- 以英文逗号分隔的字符串 --> <KeepAlive include="a,b"> <component :is="view" /> </KeepAlive> <!-- 正则表达式 (需使用 `v-bind`) --> <KeepAlive :include="/a|b/"> <component :is="view" /> </KeepAlive> <!-- 数组 (需使用 `v-bind`) --> <KeepAlive :include="['a', 'b']"> <component :is="view" /> </KeepAlive>
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
在这里插入代码片
- max - 数字。最多可以缓存多少组件实例
如果缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间。
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>
注:1. 是根据组件的 name 选项进行匹配,所以组件想要条件性地被 KeepAlive 缓存,必须显式声明一个 name 选项。
注:2. 在 3.2.34 或以上的版本中,使用
2.KeepAlive的生命周期
当一个组件实例从 DOM 上移除但因为被 缓存而仍作为组件树的一部分时,它将变为不活跃状态而不是被卸载。当一个组件实例作为缓存树的一部分插入到 DOM 中时,它将重新被激活。
- onActivated 当组件被激活(使用)的时候触发 可以简单理解为进入这个页面的时候触发
- 当组件不被使用(inactive状态)的时候触发 可以简单理解为离开这个页面的时候触发
<script setup> import { onActivated, onDeactivated } from 'vue' onActivated(() => { // 调用时机为首次挂载 // 以及每次从缓存中被重新插入时 }) onDeactivated(() => { // 在从 DOM 上移除、进入缓存 // 以及组件卸载时调用 }) </script>
请注意:
-
onActivated 在组件挂载时也会调用,并且 onDeactivated 在组件卸载时也会调用。
-
onActivated 和 onDeactivated 钩子不仅适用于
<KeepAlive>
缓存的根组件,也适用于缓存树中的后代组件。keep-alive 缓存的组件,会多出两个生命周期钩子(activated与deactivated):
- 首次进入组件时:beforeRouteEnter > beforeCreate > created> mounted > activated > … … > beforeRouteLeave > deactivated
- 再次进入组件时:beforeRouteEnter >activated > … … > beforeRouteLeave > deactivated
二、使用场景
- 切换tab,进行缓存,但又希望可以刷新数据
解决办法:- 给用户机会触发刷新,增加下拉加载刷新事件
- 将获取数据的操作写在activated步骤
- 前进刷新,后退缓存用户浏览数据
- 搜索页面 => 到搜索结果页时,搜索结果页面要重新获取数据,
- 搜索结果页面 => 点击进入详情页 => 从详情页返回列表页时,要保存上次已经加载的数据和自动还原上次的浏览位置。
<keep-alive> <router-view v-if="useRoute.meta.keepAlive"/> </keep-alive> <router-view v-if="!useRoute.meta.keepAlive"/> // list是我们的搜索结果页面 // router.js { path: '/list', name: 'List', component: List, meta: { isUseCache: false, // 默认不缓存 keepAlive: true // 是否使用 keep-alive } } // 组件中运用route import { useRoute } from 'vue-router'; const useRoute = useRoute (); let list = ref([]); // list组件的activated钩子 activated() { //isUseCache为false时才重新刷新获取数据 //因为对list使用keep-alive来缓存组件,所以默认是会使用缓存数据的 if(!useRoute.meta.isUseCache){ list.value = []; // 清空原有数据 onLoad(); // 舒心数据 } useRoute.meta.isUseCache = false // 通过这个控制刷新 }, // list组件的beforeRouteLeave钩子函数 // 跳转到详情页时,设置需要缓存 => beforeRouterLeave:离开当前路由时 => 导航在离开该组件的对应路由时调用,可以访问组件实例 用来禁止用户离开,比如还未保存草稿,或者在用户离开前把定时器销毁 beforeRouteLeave(to, from, next){ if(to.name=='Detail'){ from.meta.isUseCache = true } next() }
- 事件绑定了很多次,比如上传点击input监听change事件,突然显示了多张相同图片的问题
也就是说,DOM在编译后就缓存在内容中了,如果再次进入还再进行事件绑定初始化则就会发生这个问题
解决办法: 在mounted中绑定事件,因为这个只执行一次,并且DOM已准备好。如果插件绑定后还要再执行一下事件的handler函数的话,那就提取出来,放在activated中执行。比如:根据输入内容自动增长textarea的高度,这部分需要监听textarea的input和change事件,并且页面进入后还要再次执行一次handler函数,更新textarea高度(避免上次输入的影响)。
三、源码
keep-alive是vue中内置的一个组件
const getCurrentInstance = () => currentInstance || currentRenderingInstance;
const KeepAliveImpl = {
name: `KeepAlive`,
//用于在渲染器内部进行特殊处理的标记。我们没有使用===
//在渲染器中直接选中KeepAlive,因为直接导入
//可以防止它被tree-shaken。
__isKeepAlive: true,
props: {
include: [String, RegExp, Array],
exclude: [String, RegExp, Array],
max: [String, Number]
},
setup(props, { slots }) {
const instance = getCurrentInstance();
// KeepAlive communicates with the instantiated renderer via the
// ctx where the renderer passes in its internals,
// and the KeepAlive instance exposes activate/deactivate implementations.
// The whole point of this is to avoid importing KeepAlive directly in the
// renderer to facilitate tree-shaking.
const sharedContext = instance.ctx;
// if the internal renderer is not registered, it indicates that this is server-side rendering,
// for KeepAlive, we just need to render its children
if (!sharedContext.renderer) {
return slots.default;
}
const cache = new Map();
const keys = new Set();
let current = null;
{
instance.__v_cache = cache;
}
const parentSuspense = instance.suspense;
const { renderer: { p: patch, m: move, um: _unmount, o: { createElement } } } = sharedContext;
const storageContainer = createElement('div');
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
const instance = vnode.component;
move(vnode, container, anchor, 0 /* ENTER */, parentSuspense);
// in case props have changed
patch(instance.vnode, vnode, container, anchor, instance, parentSuspense, isSVG, vnode.slotScopeIds, optimized);
queuePostRenderEffect(() => {
instance.isDeactivated = false;
if (instance.a) {
invokeArrayFns(instance.a);
}
const vnodeHook = vnode.props && vnode.props.onVnodeMounted;
if (vnodeHook) {
invokeVNodeHook(vnodeHook, instance.parent, vnode);
}
}, parentSuspense);
{
// Update components tree
devtoolsComponentAdded(instance);
}
};
sharedContext.deactivate = (vnode) => {
const instance = vnode.component;
move(vnode, storageContainer, null, 1 /* LEAVE */, parentSuspense);
queuePostRenderEffect(() => {
if (instance.da) {
invokeArrayFns(instance.da);
}
const vnodeHook = vnode.props && vnode.props.onVnodeUnmounted;
if (vnodeHook) {
invokeVNodeHook(vnodeHook, instance.parent, vnode);
}
instance.isDeactivated = true;
}, parentSuspense);
{
// Update components tree
devtoolsComponentAdded(instance);
}
};
function unmount(vnode) {
// reset the shapeFlag so it can be properly unmounted
resetShapeFlag(vnode);
_unmount(vnode, instance, parentSuspense);
}
function pruneCache(filter) {
cache.forEach((vnode, key) => {
const name = getComponentName(vnode.type);
if (name && (!filter || !filter(name))) {
pruneCacheEntry(key);
}
});
}
function pruneCacheEntry(key) {
const cached = cache.get(key);
if (!current || cached.type !== current.type) {
unmount(cached);
}
else if (current) {
// current active instance should no longer be kept-alive.
// we can't unmount it now but it might be later, so reset its flag now.
resetShapeFlag(current);
}
cache.delete(key);
keys.delete(key);
}
// prune cache on include/exclude prop change
watch(() => [props.include, props.exclude], ([include, exclude]) => {
include && pruneCache(name => matches(include, name));
exclude && pruneCache(name => !matches(exclude, name));
},
// prune post-render after `current` has been updated
{ flush: 'post', deep: true });
// cache sub tree after render
let pendingCacheKey = null;
const cacheSubtree = () => {
// fix #1621, the pendingCacheKey could be 0
if (pendingCacheKey != null) {
cache.set(pendingCacheKey, getInnerChild(instance.subTree));
}
};
onMounted(cacheSubtree);
onUpdated(cacheSubtree);
onBeforeUnmount(() => {
cache.forEach(cached => {
const { subTree, suspense } = instance;
const vnode = getInnerChild(subTree);
if (cached.type === vnode.type) {
// current instance will be unmounted as part of keep-alive's unmount
resetShapeFlag(vnode);
// but invoke its deactivated hook here
const da = vnode.component.da;
da && queuePostRenderEffect(da, suspense);
return;
}
unmount(cached);
});
});
return () => {
pendingCacheKey = null;
if (!slots.default) {
return null;
}
const children = slots.default();
const rawVNode = children[0];
if (children.length > 1) {
{
warn$1(`KeepAlive should contain exactly one component child.`);
}
current = null;
return children;
}
else if (!isVNode(rawVNode) ||
(!(rawVNode.shapeFlag & 4 /* STATEFUL_COMPONENT */) &&
!(rawVNode.shapeFlag & 128 /* SUSPENSE */))) {
current = null;
return rawVNode;
}
let vnode = getInnerChild(rawVNode);
const comp = vnode.type;
// for async components, name check should be based in its loaded
// inner component if available
const name = getComponentName(isAsyncWrapper(vnode)
? vnode.type.__asyncResolved || {}
: comp);
const { include, exclude, max } = props;
if ((include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))) {
current = vnode;
return rawVNode;
}
const key = vnode.key == null ? comp : vnode.key;
const cachedVNode = cache.get(key);
// clone vnode if it's reused because we are going to mutate it
if (vnode.el) {
vnode = cloneVNode(vnode);
if (rawVNode.shapeFlag & 128 /* SUSPENSE */) {
rawVNode.ssContent = vnode;
}
}
// #1513 it's possible for the returned vnode to be cloned due to attr
// fallthrough or scopeId, so the vnode here may not be the final vnode
// that is mounted. Instead of caching it directly, we store the pending
// key and cache `instance.subTree` (the normalized vnode) in
// beforeMount/beforeUpdate hooks.
pendingCacheKey = key;
if (cachedVNode) {
// copy over mounted state
vnode.el = cachedVNode.el;
vnode.component = cachedVNode.component;
if (vnode.transition) {
// recursively update transition hooks on subTree
setTransitionHooks(vnode, vnode.transition);
}
// avoid vnode being mounted as fresh
vnode.shapeFlag |= 512 /* COMPONENT_KEPT_ALIVE */;
// make this key the freshest
keys.delete(key);
keys.add(key);
}
else {
keys.add(key);
// prune oldest entry
if (max && keys.size > parseInt(max, 10)) {
pruneCacheEntry(keys.values().next().value);
}
}
// avoid vnode being unmounted
vnode.shapeFlag |= 256 /* COMPONENT_SHOULD_KEEP_ALIVE */;
current = vnode;
return rawVNode;
};
}
};
const KeepAlive = KeepAliveImpl;
注意:服务器端渲染期间avtived不被调用
四、缓存后如何获取数据
解决方案可以有以下两种:
- beforeRouteEnter
- actived
beforeRouteEnter
每次组件渲染的时候,都会执行beforeRouteEnter
activedbeforeRouteEnter(to, from, next){ next(vm=>{ console.log(vm) // 每次进入路由执行 vm.getData() // 获取数据 }) },
在keep-alive缓存的组件被激活的时候,都会执行actived钩子activated(){ this.getData() // 获取数据 },
注意:服务器端渲染期间avtived不被调用
到此这篇关于vue中的keep-alive详解与应用场景的文章就介绍到这了,更多相关vue keep-alive内容请
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » vue3.0(十四)内置组件KeepAlive
发表评论 取消回复