找回密码
 立即注册
首页 业界区 业界 vue 不完美的多标签页解决方案

vue 不完美的多标签页解决方案

任修 2025-9-26 10:56:09
1.png

开源地址:https://github.com/Montaro2017/vue-tag-view
背景

多标签页多用在中后台管理系统,能够让用户同时打开多个标签页,而且不会丢失之前填写的内容,操作起来会比较方便。虽然部分开源项目有多标签页的功能,但就体验来看,算不上特别好。
目标


  • 可以通过router.push实现打开标签页
  • 同一路由组件可以多开并且数据能够缓存下来
  • 不需要处理是否缓存导致的生命周期不一致的问题
  • 多标签页可以关闭,同时KeepAlive中的缓存清除
存在的问题

要实现多标签页的缓存,最简单的方法就是用RouterView配合KeepAlive。
  1. <RouterView v-slot="{ Component }">
  2.   <KeepAlive>
  3.     <component :is="Component" />
  4.   </KeepAlive>
  5. </RouterView>
复制代码
然而,这个方案存在几个问题:

  • 不能重复打开同一个路由,而是原有的组件被激活
  • 组件生命周期发生变化
不能重复打开路由

如果给路由添加参数,打开第一次没有任何问题,但如果换另一个参数打开,还会是之前的页面,因为组件被缓存下来了。
例如:
新增一个路由 counter,在页面上添加RouterLink,并使用不同的参数
  1.       [align=center] 2.jpg [/align]<RouterView v-slot="{ Component }">
  2.   <KeepAlive>
  3.     <component :is="Component" />
  4.   </KeepAlive>
  5. </RouterView><RouterView v-slot="{ Component }">
  6.   <KeepAlive>
  7.     <component :is="Component" />
  8.   </KeepAlive>
  9. </RouterView><RouterView v-slot="{ Component }">
  10.   <KeepAlive>
  11.     <component :is="Component" />
  12.   </KeepAlive>
  13. </RouterView>Home<RouterView v-slot="{ Component }">
  14.   <KeepAlive>
  15.     <component :is="Component" />
  16.   </KeepAlive>
  17. </RouterView>About<RouterView v-slot="{ Component }">
  18.   <KeepAlive>
  19.     <component :is="Component" />
  20.   </KeepAlive>
  21. </RouterView>Counter 1<RouterView v-slot="{ Component }">
  22.   <KeepAlive>
  23.     <component :is="Component" />
  24.   </KeepAlive>
  25. </RouterView>Counter 2<RouterView v-slot="{ Component }">
  26.   <KeepAlive>
  27.     <component :is="Component" />
  28.   </KeepAlive>
  29. </RouterView><RouterView v-slot="{ Component }">
  30.   <KeepAlive>
  31.     <component :is="Component" />
  32.   </KeepAlive>
  33. </RouterView>   
复制代码
然后再Counter组件中获取id参数,分别点击Counter 1和Counter 2,会发现点击Counter 1时获取到的id是1,点击Counter 2时却没有任何变化,而且两个RouterLink同时是激活状态。
3.png

组件生命周期变化

和上一个问题有所关联,因为组件没有重新加载,在需要重新获取数据时,KeepAlive改变了组件的生命周期,添加了onActivated和onDeactivated生命周期。
添加一个组件测试生命周期:
  1. <template>
  2.   
  3.     <h1>This is an about page</h1>
  4.   
  5. </template>
复制代码
再修改App.vue
  1.       [align=center] 4.jpg [/align]<RouterView v-slot="{ Component }">
  2.   <KeepAlive>
  3.     <component :is="Component" />
  4.   </KeepAlive>
  5. </RouterView><RouterView v-slot="{ Component }">
  6.   <KeepAlive>
  7.     <component :is="Component" />
  8.   </KeepAlive>
  9. </RouterView><RouterView v-slot="{ Component }">
  10.   <KeepAlive>
  11.     <component :is="Component" />
  12.   </KeepAlive>
  13. </RouterView>Home<RouterView v-slot="{ Component }">
  14.   <KeepAlive>
  15.     <component :is="Component" />
  16.   </KeepAlive>
  17. </RouterView>About<RouterView v-slot="{ Component }">
  18.   <KeepAlive>
  19.     <component :is="Component" />
  20.   </KeepAlive>
  21. </RouterView>Counter 1<RouterView v-slot="{ Component }">
  22.   <KeepAlive>
  23.     <component :is="Component" />
  24.   </KeepAlive>
  25. </RouterView>Counter 2<RouterView v-slot="{ Component }">
  26.   <KeepAlive>
  27.     <component :is="Component" />
  28.   </KeepAlive>
  29. </RouterView><RouterView v-slot="{ Component }">
  30.   <KeepAlive>
  31.     <component :is="Component" />
  32.   </KeepAlive>
  33. </RouterView><RouterView v-slot="{ Component }">
  34.   <KeepAlive>
  35.     <component :is="Component" />
  36.   </KeepAlive>
  37. </RouterView>      
复制代码
先从Home切换到About再切换回Home再切换回About。
查看在不使用KeepAlive切换页面时候的输出,onBeforeMount -> onMounted -> onBeforeUnmount -> onUnMounted 循环
5.png

使用KeepAlive的情况,情况就复杂很多,每次切换到页面时会激活onActivated钩子,正常情况下可以通过onActivated钩子获取路由参数,重新获取数据。
问题在于:如果组件可以在缓存与不缓存中切换,在获取数据时,需要考虑是写在onMounted里还是onActivated里,写在onMounted中时如果组件会被服用,需要处理路由参数变化重新获取数据;写在onActivated里,需要考虑组件不缓存了钩子函数不会被调用的情况。
6.png

解决方案

重复打开组件 & 生命周期变化

这个问题很好解决,只需要给KeepAlive中的component加上不同的key就可以实现,key可以通过router.fullPath来计算,这样KeepAlive中就可以缓存同一个组件多次。
  1. <RouterView v-slot="{ Component }">
  2.   <KeepAlive>
  3.     <component :is="Component" />
  4.   </KeepAlive>
  5. </RouterView>
复制代码
7.png

同时,修改下Counter组件,查看生命周期
  1. <template>
  2.      ID = {{ id }}
  3. </template>
复制代码
会发现,虽然是同一个组件,但生命周期也独立了,也就不需要考虑路由参数变化时重新获取数据,只需要在onMounted时获取一次数据就可以了。
8.png

关闭标签页

上面的问题好像一下就解决了,但第三个目标没有实现,这也是最难的一个问题。
KeepAlive可以通过给component添加不同的key达到路由多开的效果,但是却不能用key删除,KeepAlive只能通过exclude参数使用组件名称删除缓存。
这下问题麻烦了,虽然使用不同的key多开了路由,但路由的组件名称是相同的,也就是说,就算能多开了,关闭却只能全部关闭,这种是不行的。
思索后,想到了下面的方案:
不使用KeepAlive,通过监听route,变化后就向list中添加达到打开标签页的功能,渲染list中的所有组件,然后为了让组件数据缓存下来,不能使用v-if而是使用v-show来隐藏组件。
验证方案

监听route,将访问过的路由都保存下来作为打开过的标签页,当前route作为激活的标签页
编写一个TagView组件,替代RouterView+KeepAlive,关闭的时候直接删除tagView就可以
  1. <RouterView v-slot="{ Component }">
  2.   <KeepAlive>
  3.     <component :is="Component" />
  4.   </KeepAlive>
  5. </RouterView>    {{ tagView.title }}<RouterView v-slot="{ Component }">
  6.   <KeepAlive>
  7.     <component :is="Component" />
  8.   </KeepAlive>
  9. </RouterView><RouterView v-slot="{ Component }">
  10.   <KeepAlive>
  11.     <component :is="Component" />
  12.   </KeepAlive>
  13. </RouterView>   
复制代码
然后在App.vue中使用
  1.       Counter 1    Counter 2<RouterView v-slot="{ Component }">
  2.   <KeepAlive>
  3.     <component :is="Component" />
  4.   </KeepAlive>
  5. </RouterView>  
复制代码
样式随便写的,明白意思就好。
可以自由切换标签页,并且填写的内容依然保留。
9.png

优点:编写起来很简单
缺点:之前的组件一直保留,打开的页面多了可能会卡
总结:也算一种可行的方案,但要注意页面不能太多
10.png

之前的组件只是display: none了
可能是优化

上面其实解决了最大的问题,但是还可以优化一下,可以利用KeepAlive卸载dom并缓存。
基于上面的方案,在Component外面再套一层KeepAlive,然后将v-show改成v-if。
  1. <RouterView v-slot="{ Component }">
  2.   <KeepAlive>
  3.     <component :is="Component" />
  4.   </KeepAlive>
  5. </RouterView>    {{ tagView.title }}<RouterView v-slot="{ Component }">
  6.   <KeepAlive>
  7.     <component :is="Component" />
  8.   </KeepAlive>
  9. </RouterView><RouterView v-slot="{ Component }">
  10.   <KeepAlive>
  11.     <component :is="Component" />
  12.   </KeepAlive>
  13. </RouterView><RouterView v-slot="{ Component }">
  14.   <KeepAlive>
  15.     <component :is="Component" />
  16.   </KeepAlive>
  17. </RouterView><RouterView v-slot="{ Component }">
  18.   <KeepAlive>
  19.     <component :is="Component" />
  20.   </KeepAlive>
  21. </RouterView>  
复制代码
11.png

12.png

这样就解决了打开页面太多可能会导致的性能问题,但是在DevTool中就会看到很多个KeepAlive了,这也是一种取舍吧。
总结

上面的解决方案并不完美,要么容易影响性能,要么可能会影响开发(多个KeepAlive在DevTool里),要完美的话估计只能自己实现一个KeepAlive了。
我正在使用免费的纯真社区版IP库。纯真(CZ88.NET)自2005年起一直为广大社区用户提供社区版IP地址库,只要获得纯真的授权就能免费使用,并不断获取后续更新的版本。如果有需要免费版IP库的朋友可以前往纯真的官网进行申请。
纯真除了免费的社区版IP库外,还提供数据更加准确、服务更加周全的商业版IP地址查询数据。纯真围绕IP地址,基于 网络空间拓扑测绘 + 移动位置大数据 方案,对IP地址定位、IP网络风险、IP使用场景、IP网络类型、秒拨侦测、VPN侦测、代理侦测、爬虫侦测、真人度等均有近20年丰富的数据沉淀。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册