vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
208.01k stars 33.69k forks source link

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

Closed okjesse closed 7 years ago

okjesse commented 7 years ago

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)

F5F5 commented 3 years ago

@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然后手动移除缓存并销毁内存还是一直在上涨,不知道有没有关注这方面?

我也发现了这个问题,在论坛上提了个问 https://forum.vuejs.org/t/keepalive/109030 不知道有没有大佬能解答一下。。。

Aaminly commented 3 years ago

我这里的场景是点击导航的时候不缓存,但是页面提供了一个返回上级页面,这时取缓存;且详情页返回到列表页也取缓存,目前是实现了需求。

const cacheThis = new Map();
Vue.mixin({
    async beforeRouteLeave(to, from, next) {
        const isNavClick = (await sessionStorage.getItem('isNavClick')) === '1'
        if (isNavClick) {
            if (cacheThis.has(to.name)) {
                cacheThis.get(to.name).$destroy()
            } else {
                cacheThis.set(from.name, this)
            }
        }
        sessionStorage.setItem('isNavClick', '0');
        next()
    }
})

原理就是通过mixin混入一个组件内路由跳转前的守卫,缓存当前组件的this实例到一个map对象里,当点击导航的时候获取一下是否缓存了当前要去的这个实例,如果有,则调用这个实例的destory销毁缓存,达到重新渲染的目的。其他的情况都取keep-alive缓存。

elewen commented 3 years ago

讨论了快4年了,这个问题还没解决啊???比较明显的一个问题是 keep-alive 应该缺少两个必不可少的配套方法:clear 方法用来清空 keep-alive 实例所属的所有已缓存组件或页面;remove() 方法用来移除 keep-alive 实例所属的某个已缓存组件或页面;

elewen commented 3 years ago

暴力操作 keys 和 cache,并用 $destroy 销毁实例,很好用。

zero7u commented 3 years ago

希望官方打开并解决,确实有问题! “多个路由对应一个组件” 这不是include能解决的事

turkyden commented 3 years ago

How can i destory the component cache by myself ?

In this way ! @elewen @zero7u

<!-- Tabs.vue -->
<template>
  <router-link>
     Tab A
     <span @click.prevent.stop="closeSelectedTag(tag)" />
   </router-link>
</template>
<script>
export default {
  computed: {
    cache: {
      get() {
        if (!this.$route.matched[1]) return 
        const instances = this.$route.matched[1].instances;
        return instances.default.$vnode.parent.componentInstance.cache;
      },
      set(val) {
        this.$route.matched[1].instances.default.$vnode.parent.componentInstance.cache = val
      }
    }
  },
  methods: {
    closeSelectedTag(view) {
      // Remove selected tag 
      // ...
      // Remove the cache from cache
      const cache = this.cache;
      const str = RegExp('.*' + view.fullPath);
      let key = '';
      Object.keys(cache).forEach(el => {
        if (str.test(el)) {
          key = el;
        }
      });
      delete cache[key];
    }
  }
}
</script>
baohangxing commented 3 years ago

讨论了快4年了,这个问题还没解决啊???比较明显的一个问题是 keep-alive 应该缺少两个必不可少的配套方法:clear 方法用来清空 keep-alive 实例所属的所有已缓存组件或页面;remove() 方法用来移除 keep-alive 实例所属的某个已缓存组件或页面;

不能赞同更多

betgar commented 3 years ago

Custom cache strategy and matching rules for KeepAlive https://github.com/vuejs/rfcs/pull/284

turkyden commented 3 years ago

b

zhangchugao commented 2 years ago

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

我也是这个疑问,include无法满足这个需求,多个路由页面对应一个组建,那就只有一个组件name,咋办?

自己实现一个keep-alive组件替换vue的keep-alive,这篇知乎文章有介绍:https://zhuanlan.zhihu.com/p/269385782,但是需要自己将keep-alive的缓存key从name修改成根据路径path进行缓存

fanfanjiang commented 2 years ago

@zhangchugao 你好,你发的链接好像失效了,能再贴一下吗,谢谢

zhangchugao commented 2 years ago

@zhangchugao 你好,你发的链接好像失效了,能再贴一下吗,谢谢

没有失效的。https://zhuanlan.zhihu.com/p/269385782

zhongguodong commented 2 years ago

@zhangchugao 你好,你的解决方案非常好,和我的问题一模一样。但是有个小小问题,路由名字也是一样的。路由地址也是一样的,但是参数不一样,两个页面的,分别缓存起来。

例如: http://localhost/#/ehr/parameter/index/3 http://localhost/#/ehr/parameter/index/13

路由信息:

  {
    path: "/ehr/parameter",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "index/:wid(\\d+)",
        component: resolve =>
          require(["@/views/datamodel/winparameter/index"], resolve),
        name: "WinParameter",
        meta: { title: "加载参数", activeMenu: "/datamodel/winparameter" }
      }
    ]
  }

标签卡的组件页面,全部都空白页面了 image

zhangchugao commented 2 years ago

@zhangchugao 你好,你的解决方案非常好,和我的问题一模一样。但是有个小小问题,路由名字也是一样的。路由地址也是一样的,但是参数不一样,两个页面的,分别缓存起来。

例如: http://localhost/#/ehr/parameter/index/3 http://localhost/#/ehr/parameter/index/13

路由信息:

  {
    path: "/ehr/parameter",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "index/:wid(\\d+)",
        component: resolve =>
          require(["@/views/datamodel/winparameter/index"], resolve),
        name: "WinParameter",
        meta: { title: "加载参数", activeMenu: "/datamodel/winparameter" }
      }
    ]
  }

标签卡的组件页面,全部都空白页面了 image

你好,我这边测试了,发现没有问题,请问你是否修改了自定义keep-alive组件js的缓存key从name修改为更具path路径缓存,原作者贴的代码当中使用的组件的name来缓存对应的每个路由的。我修改如下图所示: image 你可以查看你是否修改了图片中的代码。 1652163725 1652163725(1)

zhongguodong commented 2 years ago

@zhangchugao 你好,你的解决方案非常好,和我的问题一模一样。但是有个小小问题,路由名字也是一样的。路由地址也是一样的,但是参数不一样,两个页面的,分别缓存起来。 例如: http://localhost/#/ehr/parameter/index/3 http://localhost/#/ehr/parameter/index/13 路由信息:

  {
    path: "/ehr/parameter",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "index/:wid(\\d+)",
        component: resolve =>
          require(["@/views/datamodel/winparameter/index"], resolve),
        name: "WinParameter",
        meta: { title: "加载参数", activeMenu: "/datamodel/winparameter" }
      }
    ]
  }

标签卡的组件页面,全部都空白页面了 image

你好,我这边测试了,发现没有问题,请问你是否修改了自定义keep-alive组件js的缓存key从name修改为更具path路径缓存,原作者贴的代码当中使用的组件的name来缓存对应的每个路由的。我修改如下图所示: image 你可以查看你是否修改了图片中的代码。 1652163725 1652163725(1)

是的,我刚刚改好了,vue2测试了,有vue3版本的吗?官方一直不处理这个问题,郁闷。

zhangchugao commented 2 years ago

@zhangchugao 你好,你的解决方案非常好,和我的问题一模一样。但是有个小小问题,路由名字也是一样的。路由地址也是一样的,但是参数不一样,两个页面的,分别缓存起来。 例如: http://localhost/#/ehr/parameter/index/3 http://localhost/#/ehr/parameter/index/13 路由信息:

  {
    path: "/ehr/parameter",
    component: Layout,
    hidden: true,
    children: [
      {
        path: "index/:wid(\\d+)",
        component: resolve =>
          require(["@/views/datamodel/winparameter/index"], resolve),
        name: "WinParameter",
        meta: { title: "加载参数", activeMenu: "/datamodel/winparameter" }
      }
    ]
  }

标签卡的组件页面,全部都空白页面了 image

你好,我这边测试了,发现没有问题,请问你是否修改了自定义keep-alive组件js的缓存key从name修改为更具path路径缓存,原作者贴的代码当中使用的组件的name来缓存对应的每个路由的。我修改如下图所示: image 你可以查看你是否修改了图片中的代码。 1652163725 1652163725(1)

是的,我刚刚改好了,vue2测试了,有vue3版本的吗?官方一直不处理这个问题,郁闷。 不好意思,暂时没有,你可以自己尝试。

Hansomeble commented 2 years ago

这是来自QQ邮箱的假期自动回复邮件。   您好,我现在没在上网,无法亲自回复您的邮件。我会在上网后,尽快给您回复。

mogazheng commented 2 years ago

真的服了,到现在还不能手动清keepalive?vue3又把$destroy去了,嘛呢,开倒车呢?

betgar commented 2 years ago

https://github.com/vuejs/rfcs/pull/284