Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

希望keep-alive能增加可以动态删除已缓存组件的功能 #6509

Closed
okjesse opened this issue Sep 4, 2017 · 40 comments

Comments

Projects
None yet
@okjesse
Copy link

commented Sep 4, 2017

What problem does this feature solve?

我在使用vue做单页应用时,在多个功能切换的时候,希望能达到动态创建tab进行切换的效果,和以前使用iframe一样,这些tab是keep-alive的,但如果太多keep-alive的时候,浏览器内存占用会过大。我希望能够达到可切换的tab最多例如只有十个,前面的我会用程序自动关闭对应tab,此时希望能把其缓存的组件也对应清除。

What does the proposed API look like?

例如:vm.$clearKeepAlived(routerName)

@jkzing

This comment has been minimized.

Copy link
Member

commented Sep 4, 2017

其实这个问题之前在forum上回答过一次,这件事可以用keep-aliveinclude或者exclude做到。

给你写个简单的例子:
https://jsfiddle.net/jingkaizhao/m5tLg7wt/2/

@posva

This comment has been minimized.

Copy link
Member

commented Sep 4, 2017

isn't this a dup of #6259 ?

@jkzing

This comment has been minimized.

Copy link
Member

commented Sep 5, 2017

@posva Yes, similar situation. It's about to dynamically clear keep-alive cache during runtime.

@okjesse

This comment has been minimized.

Copy link
Author

commented Sep 5, 2017

@jkzing 非常感谢,我上面说的场景您的方法应该可以完成,但我还有个场景,例如列表页面,如果是从tab切换过来的时候,希望是keep-alive,直接从内存拿数据,如果是修改一条数据后,程序跳转到列表页面,希望是能够后台获取新的数据,虽然我可以页面完成后再从后台获取,但感觉不够优雅,如果有上述api,我只需要在修改后把这个router的keep-alive清除,数据应该就会自动从后台获取了。不知道我表述的是否能够让您明白,总之,我希望能够优雅的把单页应用做成和以前iframe的多tab效果一样: http://www.sucaihuo.com/modals/16/1653/demo/ 以上,谢谢。

@jkzing

This comment has been minimized.

Copy link
Member

commented Sep 5, 2017

@okjesse 这个也是可以做到的,你需要的只是把include或者exclude提升到更上层组件;或者,如果用了vuex的话把include放到vuex store中。

总之,vue提供了基础的API来完成这件事,想方便地用起来的话需要稍微封装下。

@okjesse

This comment has been minimized.

Copy link
Author

commented Sep 5, 2017

@jkzing 明白,谢谢了,我现在是自己写了个keep-alive实现的,我试试 按照您的方法

@okjesse

This comment has been minimized.

Copy link
Author

commented Sep 5, 2017

@jkzing ,不过就算这样能够实现,我觉得最好还是得有个这个api,语义会不太一样,我所希望的是清除已经keep-alive的数据,组件本身还是alive的,而不是不停的切换是否会alive

@tomatoo1

This comment has been minimized.

Copy link

commented Sep 5, 2017

这只是其中一个问题,我们遇到的不停开新页面,会导致内存过大,最后浏览器卡死的问题,应该和keepalive无关。如果可以,我们重现个现象给@jkzing

@tomatoo1

This comment has been minimized.

Copy link

commented Sep 5, 2017

我们要实现的是,在跳转到页面的前一刻才决定是否用缓存的页面,还是新页面。老的api做不到的吧

@fadexiii

This comment has been minimized.

Copy link

commented Sep 20, 2017

我目前也遇到了这个需求,再菜单列表点击连接,在tabs里面增加一个标签并缓存新开的组件;点击关闭标签我希望能删除缓存的组件;我是在组件里面deactivated钩子函数调用this.$destroy();这样能清除缓存,但是发现下次打开这个连接的时候,新的组件不会被缓存了

@okjesse

This comment has been minimized.

Copy link
Author

commented Sep 21, 2017

@fadexiii 现有的方法我解决了,通过动态设置includes,可以实现

@fadexiii

This comment has been minimized.

Copy link

commented Sep 21, 2017

@okjesse 我的keep-alive 里面包含的是一个<router-view></router-view>用includes的方法不起作用

@fadexiii

This comment has been minimized.

Copy link

commented Sep 21, 2017

@okjesse 刚刚是我没有定义conponent的name属性,定义之后可以了,多谢

@okjesse okjesse closed this Sep 21, 2017

@yyx990803

This comment has been minimized.

Copy link
Member

commented Oct 6, 2017

FYI: implemented in 2cba6d4

@Jack-93

This comment has been minimized.

Copy link

commented Oct 31, 2017

@fadexiii #6961 和你一样的需求,请问下你们怎么解决的呀

@fadexiii

This comment has been minimized.

Copy link

commented Oct 31, 2017

@Jack-93 利用keep-alive的include,新打开标签时,把当前组件名加入到include数组里,关闭标签时从数组中删除关闭标签的组件名就可以了

@wanyaxing

This comment has been minimized.

Copy link

commented Nov 29, 2017

看到$destroy这个方法,我觉得这就是正确的用法,通过组件里不同的用户行为决定离开页面时是否缓存组件。
(比如一个留言组件,留言提交成功后这个组件应该销毁,如果没有提交,则应该保留缓存,让用户下次进来继续。)
然而,destroy之后,再次重复进入这个组件会出现奇怪的结果,组件不能再被缓存了,我觉得这是bug吧。

@realfly08

This comment has been minimized.

Copy link

commented Dec 9, 2017

@wanyaxing 我也遇到过你类似的问题
我用了keep-alive 缓存所有的子组件,列表页A---》详情页B---》三级页面D, 在正向前进的时候,我将打开的路由全部存在sessionStorage里面,返回的时候判断是下一个路由是否在sessionStorage里面,如果在的话,就将当前实例销毁。
这样的话,第一次没问题,能正常运行,两次或者多次以后,就会出现会B被缓存多次的情况,每次从D返回至B的时候,会生成一个新的B,而之前缓存的B还一直在内存当中,如图所示:
image

核心代码如下:
image

@wanyaxing

This comment has been minimized.

Copy link

commented Dec 10, 2017

@realfly08 我尝试提交了一个PR去解决这个问题,不过好像提交失败了,我没搞清楚怎么通过vue的自动代码审查-。- #7151

我之前也是准备全站缓存,然后碰到了缓存更新的逻辑大坑。
现在我放弃了全站缓存,只在关键的几个表单页开启了缓存,然后在代码里动态判断是否保留当页数据,算是临时跳过了这些问题,然而我自己还是感觉这个逻辑不够爽利。

@hujianlong

This comment has been minimized.

Copy link

commented Dec 14, 2017

@wanyaxing 我也遇到了类似的问题,要实现的逻辑功能,A->B->C 3个页面, 返回的时候使用缓存,再次进入的时候重新渲染,目前是每个页面都keep alive, 然后我在B页面进行判断如果是返回上一级页面(go -1)就在deactivated中销毁当前组件,调用$destroy,当按顺序再次进入到C页面,返回到B页面,可以看到又重新创建了B页面,同时缓存中还存在着一个B页面

@realfly08

This comment has been minimized.

Copy link

commented Dec 25, 2017

@bigggge

This comment has been minimized.

Copy link

commented Dec 28, 2017

@fadexiii keep-alive 配合router-view怎么用?

  <keep-alive :include="includes">
     <router-view></router-view>
  </keep-alive>

includes 写的是路由配置中的name: 'login'

const router = new Router({
  routes: [
    {
      path: '/login',
      name: 'login',
      component: Login
    }]
 })

为什么我把 includes 赋值为空字符串,依然可以缓存?我的需求就是需要手动销毁缓存的路由

@leixu2txtek

This comment has been minimized.

Copy link

commented Jul 10, 2018

在组件被销毁之后,缓存并没有被清空掉,我觉得问题是在这里。
项目负责人推荐使用 include 这样的属性来处理,我觉得并没有直接destroy 来的直接吧,内存还能有所改善

@wanyaxing 看了你提交的PR ,应该是有效的,或者开放一个 remove cache 的API 也行的吧

@wanyaxing

This comment has been minimized.

Copy link

commented Jul 10, 2018

@leixu2txtek 我已经放弃了那个PR,通过强行清除缓存的方法,我变相得实现了动态删除缓存组件的功能。

我目前全站使用缓存,通过拦截页面离开的路由事件来根据业务逻辑实现删除缓存的功能,以下代码片段供参考:

  • 将router-view放到keep-alive中,默认全站默认使用缓存。
    <keep-alive><router-view class="transit-view"></router-view></keep-alive>
  • 我在routes里将所有的页面进行了分层。如meta.rank代表页面层次,如1.5>2.5>3.5意味着从第一层进入第二层进入第三层页面。
routes: [
{   path: '/', redirect:'/yingshou', },
{   path: '/yingshou',                meta:{rank:1.5,isShowFooter:true},          },
{   path: '/contract_list',           meta:{rank:1.5,isShowFooter:true},          },
{   path: '/customer',                meta:{rank:1.5,isShowFooter:true},          },
{   path: '/wode',                    meta:{rank:1.5,isShowFooter:true},          },
{   path: '/yingfu',                  meta:{rank:1.5,isShowFooter:true},          },
{   path: '/yingfu/pact_list',        meta:{rank:2.5},                            },
{   path: '/yingfu/pact_detail',      meta:{rank:3.5},                            },
{   path: '/yingfu/expend_view',      meta:{rank:4.5},                            },
{   path: '/yingfu/jizhichu',         meta:{rank:5.5},                            },
{   path: '/yingfu/select_pact',      meta:{rank:6.5},                            },
{   path: '/yingfu/jiyingfu',         meta:{rank:7.5},                            },
]
  • 因为所有页面都会缓存,所以核心思路是【何时销毁缓存?】。我的设计是:同层级页面切换或进入下一层页面都会保留当前页缓存,【返回上一层页面时则销毁当前页面缓存】。
  • 所以我在main.js里,使用Vue.mixin的方法拦截了路由离开事件,并在该拦截方法中实现了销毁页面缓存的功能。核心代码如下:

Vue.mixin({
    beforeRouteLeave:function(to, from, next){
        if (from && from.meta.rank && to.meta.rank && from.meta.rank>to.meta.rank)
        {//如果返回上一层,则摧毁本层缓存。
            if (this.$vnode && this.$vnode.data.keepAlive)
            {
                if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache)
                {
                    if (this.$vnode.componentOptions)
                    {
                        var key = this.$vnode.key == null
                                    ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '')
                                    : this.$vnode.key;
                        var cache = this.$vnode.parent.componentInstance.cache;
                        var keys  = this.$vnode.parent.componentInstance.keys;
                        if (cache[key])
                        {
                            if (keys.length) {
                                var index = keys.indexOf(key);
                                if (index > -1) {
                                    keys.splice(index, 1);
                                }
                            }
                            delete cache[key];
                        }
                    }
                }
            }
            this.$destroy();
        }
        next();
    },
});

总结:其实就是通过页面组件所在的上层keepAlive组件,暴力操控该对象中的cache列表。。。

简单又直接,虽然不是很优雅,然而很好用,哈哈:)

当然还是希望官方能支持API出来更好。

@leixu2txtek

This comment has been minimized.

Copy link

commented Jul 10, 2018

@wanyaxing 我是通过路由来控制页面的缓存

比如,我有 /executed/,以及他的子路由 /executed/chart/ ,我同样是用 mixin 方法hack 路由离开时候 销毁不需要缓存的组件,我要做到的是从父级到子集路由,父级被缓存,其他情况都被销毁,这样我们只要控制路由就行了,不用增加任何的属性什么的,我比较懒;

// 支持页面保持
Vue.mixin({
  beforeRouteLeave(to, from, next) {

    if (!to.path.startsWith(from.path)) this.$destroy();

    next();

  }
});

@wanyaxing 谢谢,你的代码对我非常有用。我参考一下;

我发现确实是缓存没有被销毁导致的。尽力在和 @LinusBorg 沟通

@leixu2txtek

This comment has been minimized.

Copy link

commented Jul 10, 2018

@wanyaxing 我一直在找拿到 cache 的方法,对vue核心代码不太熟悉,你的代码比较有用,我测试通过了,非常棒,暂时这么做,希望官方可以解决这个问题吧,我问了不少人也查了很多资料,都有这样或者那样的问题

@wanyaxing

This comment has been minimized.

Copy link

commented Jul 10, 2018

哈哈。
@leixu2txtek 在这个基础上,推荐你一个插件,搭配 https://github.com/wanyaxing/vue-router-then 组合使用,效果更好。
使用 vue-router-then 在当前页面打开子页面,可以在当前页面拿到子页面的信息或事件。
this.$routerThen.push('/chose').then(vm => {})

举一个最常用的例子,比如在填写订单页面需要打开地址列表进行选择,

<input v-model="price" v-model-link="'/select_address'" />

然后在/select_address打开的路由里,选择地址后,触发一个input事件并后退页面即可。

<script>
methods:{
    clickSomeOne:function(value){
        this.$emit('input',value);
        this.$router.go(-1);
    },
}
</script>

这需要在前文提到的缓存策略的基础上才能实现父子路由之间的直接互动,然而实现之后会非常有意思,开发起来思路很直观,不用考虑在vuex里存储临时数据,直接开页面拿数据即可。

@yiwuyu

This comment has been minimized.

Copy link

commented Jul 25, 2018

@fadexiii 你的意思是 如果使用的是router,那么include 所包含的 应该是 对应router的component中的name吗?
---- router.js ----

import ComponentA from 'views/a
export default [
  {path: 'a', component: ComponentA, name: a}
]

---- views/a.vue ----

export default {
  name: 'xxxa'
}

include 中应该存的是 ComponentA or a or xxxa ?

@fadexiii

This comment has been minimized.

Copy link

commented Jul 25, 2018

@yiwuyu xxxa

@chenliang-dragon

This comment has been minimized.

Copy link

commented Sep 3, 2018

@jkzing 如果我同一个组件加载了两次,一个需要缓存,一个不需要缓存怎么弄,他们的组件name是一样的呀

@heng1234

This comment has been minimized.

Copy link

commented Sep 11, 2018

我也遇到这个问题了 在router入口加上
meta: {
keepAlive: true // 需要被缓存
}
点击关闭的时候设置为false
接着用监听器 改为true
具体看https://mp.csdn.net/postedit/82628953 现在测试还没问题 不知道 后面会不会出现问题

@heng1234

This comment has been minimized.

Copy link

commented Sep 11, 2018

tab切换的时候缓存 关闭缓存要销毁及下次打开重新加载 多重嵌套子路由页面也是

@my3188

This comment has been minimized.

Copy link

commented Sep 15, 2018

其实这个问题之前在forum上回答过一次,这件事可以用keep-aliveinclude或者exclude做到。

给你写个简单的例子:
https://jsfiddle.net/jingkaizhao/m5tLg7wt/2/

这个例子,我看懂了。我想在这里请教下大神,我的业务是这样的,左侧是纵向的一个tabs,这个tabs是用户可以动态添加或者删除的,点击tabs中的任意一个,右侧显示和这个tab对应的一棵tree,其实是同样一棵树,只是树选中的节点不一样。最开始的想法就是添加一个tab项,右侧添加一个tree,切换tab时,tree进行相应的隐藏显示(v-show),但是这样tab比较多的时候,右侧tree也会很多,dom节点太多,如果不用隐藏显示,用v-if的话,又会存在tree的状态(选中、展开、滚动)无法保存,所以我后来想到了keep-alive,他是可以保存状态的。但是我是vue的单文件组件,tabs在主页面,tree是一个组件被import进去的,import进来后,直接在components属性里注册了,怎么才能像你发的这样,给tree创建多个名字呢?但其实我就一个组件,创建名字只是为了和左侧tab进行关联,更确切的说,是为了在左侧删除一个tab项时,也在keepalive中删除这个tab项对应的tree缓冲?可是要怎么做呢?

@Liudapeng

This comment has been minimized.

Copy link

commented Sep 25, 2018

@leixu2txtek 我已经放弃了那个PR,通过强行清除缓存的方法,我变相得实现了动态删除缓存组件的功能。

我目前全站使用缓存,通过拦截页面离开的路由事件来根据业务逻辑实现删除缓存的功能,以下代码片段供参考:

  • 将router-view放到keep-alive中,默认全站默认使用缓存。
    <keep-alive><router-view class="transit-view"></router-view></keep-alive>
  • 我在routes里将所有的页面进行了分层。如meta.rank代表页面层次,如1.5>2.5>3.5意味着从第一层进入第二层进入第三层页面。
routes: [
{   path: '/', redirect:'/yingshou', },
{   path: '/yingshou',                meta:{rank:1.5,isShowFooter:true},          },
{   path: '/contract_list',           meta:{rank:1.5,isShowFooter:true},          },
{   path: '/customer',                meta:{rank:1.5,isShowFooter:true},          },
{   path: '/wode',                    meta:{rank:1.5,isShowFooter:true},          },
{   path: '/yingfu',                  meta:{rank:1.5,isShowFooter:true},          },
{   path: '/yingfu/pact_list',        meta:{rank:2.5},                            },
{   path: '/yingfu/pact_detail',      meta:{rank:3.5},                            },
{   path: '/yingfu/expend_view',      meta:{rank:4.5},                            },
{   path: '/yingfu/jizhichu',         meta:{rank:5.5},                            },
{   path: '/yingfu/select_pact',      meta:{rank:6.5},                            },
{   path: '/yingfu/jiyingfu',         meta:{rank:7.5},                            },
]
  • 因为所有页面都会缓存,所以核心思路是【何时销毁缓存?】。我的设计是:同层级页面切换或进入下一层页面都会保留当前页缓存,【返回上一层页面时则销毁当前页面缓存】。
  • 所以我在main.js里,使用Vue.mixin的方法拦截了路由离开事件,并在该拦截方法中实现了销毁页面缓存的功能。核心代码如下:

Vue.mixin({
    beforeRouteLeave:function(to, from, next){
        if (from && from.meta.rank && to.meta.rank && from.meta.rank>to.meta.rank)
        {//如果返回上一层,则摧毁本层缓存。
            if (this.$vnode && this.$vnode.data.keepAlive)
            {
                if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache)
                {
                    if (this.$vnode.componentOptions)
                    {
                        var key = this.$vnode.key == null
                                    ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '')
                                    : this.$vnode.key;
                        var cache = this.$vnode.parent.componentInstance.cache;
                        var keys  = this.$vnode.parent.componentInstance.keys;
                        if (cache[key])
                        {
                            if (keys.length) {
                                var index = keys.indexOf(key);
                                if (index > -1) {
                                    keys.splice(index, 1);
                                }
                            }
                            delete cache[key];
                        }
                    }
                }
            }
            this.$destroy();
        }
        next();
    },
});

总结:其实就是通过页面组件所在的上层keepAlive组件,暴力操控该对象中的cache列表。。。

简单又直接,虽然不是很优雅,然而很好用,哈哈:)

当然还是希望官方能支持API出来更好。

但是这样内存还是不会释放,我关掉keepalive就没事,加上keepalive然后手动移除缓存并销毁内存还是一直在上涨,不知道有没有关注这方面?

@wanyaxing

This comment has been minimized.

Copy link

commented Sep 29, 2018

@Liudapeng 我之前没有仔细研究过内存使用,我曾粗略做过测试,在内存图时间线中看到有内存掉落后就没有细究。

最近有点忙,前两天就看到你的留言了,一直没时间给你回复。

我刚才又做了次测试,重复几次深入五六层页面然后返回到首页,内存时间线结果如下:

  • 开启 keep-alive,并动态销毁组件(即使用前文我提到的方法):

可以明显的看到时间线里有内存掉落的情况,我觉得内存是有销毁的。
snipaste_2018-09-29_17-53-09

不使用 keep-alive:

因为没有keepalive,所以内存堆叠不会很快,然而还是越来越高(也许是我代码改的匆忙,其他地方出现了内存泄露)
snipaste_2018-09-29_17-50-40

但总体而言,我觉得使用动态销毁之后,内存的使用还在可接受范围之内,所以如果你出现了内存大幅上涨的情况,应该检查是否在组件销毁时没有销毁绑定事件等原因(导致组件虽然从 keepAlive 的cache 数组里移除了,但并没有从内存销毁)。

请再检查。

@salchemist

This comment has been minimized.

Copy link

commented Oct 16, 2018

@wanyaxing i have the same problem in my project,without keep-alive:
image
Call the $destory() of dynamic component does not work,and i find that the JS heaps goes higher with the numbers of Nodes.It appears that dynamic component created DOMs without destroying them.

@Cxuyang

This comment has been minimized.

Copy link

commented Jan 14, 2019

@bigggge name是 组件 内部的属性 不是路由的属性

@sbitpdc

This comment has been minimized.

Copy link

commented Jan 23, 2019

我的问题更奇葩。我的vue版本是2.3.3,A页面有个滚动加载下一页的代码,我从A页面切换到B页面,在B页面滚动时发现,A页面的加载下一页的代码还会执行。当我把全局 keep-alive 去掉后,就不会出现上述问题了。但我所有的 activated 又失效了,得用 created。

@Cxuyang

This comment has been minimized.

Copy link

commented Jan 23, 2019

我的问题更奇葩。我的vue版本是2.3.3,A页面有个滚动加载下一页的代码,我从A页面切换到B页面,在B页面滚动时发现,A页面的加载下一页的代码还会执行。当我把全局 keep-alive 去掉后,就不会出现上述问题了。但我所有的 activated 又失效了,得用 created。

有可能是全局监听导致的 可以在滚动时判断是否为当前路由 再决定要不要加载下一页

@sbitpdc

This comment has been minimized.

Copy link

commented Jan 23, 2019

我的问题更奇葩。我的vue版本是2.3.3,A页面有个滚动加载下一页的代码,我从A页面切换到B页面,在B页面滚动时发现,A页面的加载下一页的代码还会执行。当我把全局 keep-alive 去掉后,就不会出现上述问题了。但我所有的 activated 又失效了,得用 created。

有可能是全局监听导致的 可以在滚动时判断是否为当前路由 再决定要不要加载下一页

是的,我用的是 vue-infinite-scroll 插件,当我使用 全局 keep-alive 时,它没移除掉监听事件,我已经重写这个插件了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.