vue3.0(十四)内置组件KeepAlive


一、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 中时,它将重新被激活。

  1. onActivated 当组件被激活(使用)的时候触发 可以简单理解为进入这个页面的时候触发
  2. 当组件不被使用(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

二、使用场景

  1. 切换tab,进行缓存,但又希望可以刷新数据
    解决办法:
    • 给用户机会触发刷新,增加下拉加载刷新事件
    • 将获取数据的操作写在activated步骤
  2. 前进刷新,后退缓存用户浏览数据
    • 搜索页面 => 到搜索结果页时,搜索结果页面要重新获取数据,
    • 搜索结果页面 => 点击进入详情页 => 从详情页返回列表页时,要保存上次已经加载的数据和自动还原上次的浏览位置。
      	<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()
      	}
      
  3. 事件绑定了很多次,比如上传点击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
    beforeRouteEnter(to, from, next){
        next(vm=>{
            console.log(vm)
            // 每次进入路由执行
            vm.getData()  // 获取数据
        })
    },
    
    actived
    在keep-alive缓存的组件被激活的时候,都会执行actived钩子
    activated(){
       this.getData() // 获取数据
    },
    

注意:服务器端渲染期间avtived不被调用

到此这篇关于vue中的keep-alive详解与应用场景的文章就介绍到这了,更多相关vue keep-alive内容请

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值