最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue?不完美的多標簽頁存在問題及解決方案

 更新時間:2025年07月17日 08:54:34   作者:馬卡龍  
本文給大家介紹Vue多標簽頁實現(xiàn)中的問題,如緩存沖突和生命周期管理,提出通過KeepAlive與RouterView結(jié)合的方案,但存在無法關(guān)閉單個標簽頁的缺陷,后續(xù)通過TagView組件和v-show優(yōu)化,權(quán)衡性能與開發(fā)體驗,最終建議自定義KeepAlive以實現(xiàn)更優(yōu)效果,感興趣的朋友一起看看吧

背景

多標簽頁多用在中后臺管理系統(tǒng),能夠讓用戶同時打開多個標簽頁,而且不會丟失之前填寫的內(nèi)容,操作起來會比較方便。雖然部分開源項目有多標簽頁的功能,但就體驗來看,算不上特別好。

目標

  1. 可以通過router.push實現(xiàn)打開標簽頁
  2. 同一路由組件可以多開并且數(shù)據(jù)能夠緩存下來
  3. 不需要處理是否緩存導(dǎo)致的生命周期不一致的問題
  4. 多標簽頁可以關(guān)閉,同時KeepAlive中的緩存清除

存在的問題

要實現(xiàn)多標簽頁的緩存,最簡單的方法就是用RouterView配合KeepAlive。

<RouterView v-slot="{ Component }">
  <KeepAlive>
    <component :is="Component" />
  </KeepAlive>
</RouterView>

然而,這個方案存在幾個問題:

  1. 不能重復(fù)打開同一個路由,而是原有的組件被激活
  2. 組件生命周期發(fā)生變化

不能重復(fù)打開路由

如果給路由添加參數(shù),打開第一次沒有任何問題,但如果換另一個參數(shù)打開,還會是之前的頁面,因為組件被緩存下來了。

例如:

新增一個路由 counter,在頁面上添加RouterLink,并使用不同的參數(shù)

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <nav>
        <RouterLink to="/home">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
        <RouterLink to="/counter?id=1">Counter 1</RouterLink>
        <RouterLink to="/counter?id=2">Counter 2</RouterLink>
      </nav>
    </div>
  </header>
<RouterView v-slot="{ Component }">
  <KeepAlive>
    <component :is="Component" />
  </KeepAlive>
</RouterView>
</template>

然后再Counter組件中獲取id參數(shù),分別點擊Counter 1和Counter 2,會發(fā)現(xiàn)點擊Counter 1時獲取到的id是1,點擊Counter 2時卻沒有任何變化,而且兩個RouterLink同時是激活狀態(tài)。

組件生命周期變化

和上一個問題有所關(guān)聯(lián),因為組件沒有重新加載,在需要重新獲取數(shù)據(jù)時,KeepAlive改變了組件的生命周期,添加了onActivatedonDeactivated生命周期。

添加一個組件測試生命周期:

<template>
  <div class="about">
    <h1>This is an about page</h1>
  </div>
</template>
<script setup>
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
onMounted(() => { console.log("onMounted") })
onUpdated(() => { console.log("onUpdated") })
onUnmounted(() => { console.log("onUnmounted") })
onBeforeMount(() => { console.log("onBeforeMount") })
onBeforeUpdate(() => { console.log("onBeforeUpdate") })
onBeforeUnmount(() => { console.log("onBeforeUnmount") })
onActivated(() => { console.log("onActivated") })
onDeactivated(() => { console.log("onDeactivated") })
</script>
<style>
@media (min-width: 1024px) {
  .about {
    min-height: 100vh;
    display: flex;
    align-items: center;
  }
}
</style>

再修改App.vue

<template>
  <header>
    <img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
    <div class="wrapper">
      <HelloWorld msg="You did it!" />
      <nav>
        <RouterLink to="/home">Home</RouterLink>
        <RouterLink to="/about">About</RouterLink>
        <RouterLink to="/counter?id=1">Counter 1</RouterLink>
        <RouterLink to="/counter?id=2">Counter 2</RouterLink>
      </nav>
    </div>
  </header>
  <RouterView v-slot="{ Component }">
    <!-- <KeepAlive> -->
      <component :is="Component" />
    <!-- </KeepAlive> -->
  </RouterView>
</template>
<script setup>
import { watch } from 'vue'
import { RouterLink, RouterView, useRoute } from 'vue-router'
import HelloWorld from './components/HelloWorld.vue'
const route = useRoute()
watch(route, () => {
  console.log("頁面切換", route.fullPath)
})
</script>

先從Home切換到About再切換回Home再切換回About。

查看在不使用KeepAlive切換頁面時候的輸出,onBeforeMount -> onMounted -> onBeforeUnmount -> onUnMounted 循環(huán)

使用KeepAlive的情況,情況就復(fù)雜很多,每次切換到頁面時會激活onActivated鉤子,正常情況下可以通過onActivated鉤子獲取路由參數(shù),重新獲取數(shù)據(jù)。

問題在于:如果組件可以在緩存與不緩存中切換,在獲取數(shù)據(jù)時,需要考慮是寫在onMounted里還是onActivated里,寫在onMounted中時如果組件會被服用,需要處理路由參數(shù)變化重新獲取數(shù)據(jù);寫在onActivated里,需要考慮組件不緩存了鉤子函數(shù)不會被調(diào)用的情況。

解決方案

重復(fù)打開組件 & 生命周期變化

這個問題很好解決,只需要給KeepAlive中的component加上不同的key就可以實現(xiàn),key可以通過router.fullPath來計算,這樣KeepAlive中就可以緩存同一個組件多次。

<RouterView v-slot="{ Component, route }">
  <KeepAlive>
    <component :is="Component" :key="route.fullPath" />
  </KeepAlive>
</RouterView>

同時,修改下Counter組件,查看生命周期

<template>
    <div> ID = {{ id }}</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount, onActivated, onDeactivated } from 'vue'
const route = useRoute()
const id = route.query.id
onMounted(() => { console.log(route.fullPath, "onMounted") })
onUpdated(() => { console.log(route.fullPath, "onUpdated") })
onUnmounted(() => { console.log(route.fullPath, "onUnmounted") })
onBeforeMount(() => { console.log(route.fullPath, "onBeforeMount") })
onBeforeUpdate(() => { console.log(route.fullPath, "onBeforeUpdate") })
onBeforeUnmount(() => { console.log(route.fullPath, "onBeforeUnmount") })
onActivated(() => { console.log(route.fullPath, "onActivated") })
onDeactivated(() => { console.log(route.fullPath, "onDeactivated") })
</script>

會發(fā)現(xiàn),雖然是同一個組件,但生命周期也獨立了,也就不需要考慮路由參數(shù)變化時重新獲取數(shù)據(jù),只需要在onMounted時獲取一次數(shù)據(jù)就可以了。

關(guān)閉標簽頁

上面的問題好像一下就解決了,但第三個目標沒有實現(xiàn),這也是最難的一個問題。

KeepAlive可以通過給component添加不同的key達到路由多開的效果,但是卻不能用key刪除,KeepAlive只能通過exclude參數(shù)使用組件名稱刪除緩存。

這下問題麻煩了,雖然使用不同的key多開了路由,但路由的組件名稱是相同的,也就是說,就算能多開了,關(guān)閉卻只能全部關(guān)閉,這種是不行的。

思索后,想到了下面的方案:

不使用KeepAlive,通過監(jiān)聽route,變化后就向list中添加達到打開標簽頁的功能,渲染list中的所有組件,然后為了讓組件數(shù)據(jù)緩存下來,不能使用v-if而是使用v-show來隱藏組件。

驗證方案

監(jiān)聽route,將訪問過的路由都保存下來作為打開過的標簽頁,當前route作為激活的標簽頁

編寫一個TagView組件,替代RouterView+KeepAlive,關(guān)閉的時候直接刪除tagView就可以

<template>
  <div class="tags">
    <div class="tag" v-for="tagView in tagViews" :class="{ active: tagView.key === currentTagView?.key }"
     @click="router.push(tagView.route)">
      {{ tagView.title }}</div>
  </div>
  <div class="content">
    <template v-for="tagView in tagViews" :key="tagView.key">
      <Component :is="tagView.component" v-show="tagView.key === currentTagView.key" />
    </template>
  </div>
</template>
<script setup>
import { inject, ref, shallowRef, toValue, watch } from 'vue'
import { useRoute, useRouter, viewDepthKey } from 'vue-router'
const route = useRoute()
const router = useRouter()
const tagViews = ref([])
const currentTagView = ref(null)
// 參考了vue官方的RouterView, 是RouterView嵌套的深度
const routerViewDepth = inject(viewDepthKey, 0)
const routeKey = (route) => {
  return route.fullPath
}
const routeTitle = (route) => {
  // 還沒有設(shè)計title,先用fullPath替代
  return route.fullPath
}
const toTagView = (route) => {
  const depth = toValue(routerViewDepth)
  return {
    title: routeTitle(route),
    key: routeKey(route),
    route: { ...route },
    component: shallowRef(route.matched[depth]?.components['default'])
  }
}
watch(route, () => {
  // 判斷是否已存在,存在則不添加
  const key = routeKey(route)
  let tagView = tagViews.value.find(tagView => tagView.key === key)
  if (!tagView) {
    tagView = toTagView(route)
    tagViews.value.push(tagView)
  }
  currentTagView.value = tagView
})
</script>
<style scoped>
.tags {
  gap: 8px;
  padding: 4px;
  display: flex;
  border: 1px solid #ccc;
}
.tag {
  padding: 4px 12px;
  border: 1px solid #ccc;
}
.tag.active {
  color: #fff;
  background-color: #409EFF;
}
</style>

然后在App.vue中使用

<template>
  <div class="left-menu">
    <RouterLink to="/counter?id=1">Counter 1</RouterLink>
    <RouterLink to="/counter?id=2">Counter 2</RouterLink>
  </div>
  <div class="right-content">
    <TagView />
  </div>
</template>
<script setup>
import { watch } from 'vue'
import TagView from './components/TagView.vue'
import { RouterLink, useRoute } from 'vue-router'
const route = useRoute()
watch(route, () => {
  console.log("頁面切換", route.fullPath)
})
</script>
<style scoped>
.left-menu {
  display: flex;
  padding: 8px;
  width: 220px;
  border: 1px solid #ccc;
  flex-direction: column;
}
.right-content {
  flex: 1;
  padding: 8px;
}
</style>

樣式隨便寫的,明白意思就好。

可以自由切換標簽頁,并且填寫的內(nèi)容依然保留。

優(yōu)點:編寫起來很簡單

缺點:之前的組件一直保留,打開的頁面多了可能會卡

總結(jié):也算一種可行的方案,但要注意頁面不能太多

之前的組件只是display: none了

可能是優(yōu)化

上面其實解決了最大的問題,但是還可以優(yōu)化一下,可以利用KeepAlive卸載dom并緩存。

基于上面的方案,在Component外面再套一層KeepAlive,然后將v-show改成v-if。

<template>
  <div class="tags">
    <div class="tag" v-for="tagView in tagViews" :class="{ active: tagView.key === currentTagView?.key }"
      @click="router.push(tagView.route)">
      {{ tagView.title }}</div>
  </div>
  <div class="content">
    <template v-for="tagView in tagViews" :key="tagView.key">
      <KeepAlive>
        <Component :is="tagView.component" v-if="tagView.key === currentTagView.key" />
      </KeepAlive>
    </template>
  </div>
</template>

這樣就解決了打開頁面太多可能會導(dǎo)致的性能問題,但是在DevTool中就會看到很多個KeepAlive了,這也是一種取舍吧。

總結(jié)

上面的解決方案并不完美,要么容易影響性能,要么可能會影響開發(fā)(多個KeepAlive在DevTool里),要完美的話估計只能自己實現(xiàn)一個KeepAlive了。

到此這篇關(guān)于vue 不完美的多標簽頁存在問題及解決方案的文章就介紹到這了,更多相關(guān)vue 多標簽頁內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue3項目中env文件的配置和使用指南

    Vue3項目中env文件的配置和使用指南

    在現(xiàn)代前端開發(fā)中,項目通常需要在不同的環(huán)境中運行,例如開發(fā)環(huán)境、測試環(huán)境和生產(chǎn)環(huán)境,每個環(huán)境可能需要不同的配置,通過使用?.env?文件,可以方便地管理這些環(huán)境變量,避免硬編碼配置,本文給大家介紹了Vue3項目中env文件的配置指南,需要的朋友可以參考下
    2025-03-03
  • VUE注冊全局組件和局部組件過程解析

    VUE注冊全局組件和局部組件過程解析

    這篇文章主要介紹了VUE注冊全局組件和局部組件過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-10-10
  • vue3+vite2中使用svg的方法詳解(親測可用)

    vue3+vite2中使用svg的方法詳解(親測可用)

    vue2的時候使用的是字體圖標,缺點就是比較單一,到了vue3,相信瀏覽器的性能起來,所以這里記錄一下,下面這篇文章主要給大家介紹了關(guān)于vue3+vite2中使用svg的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • Vue3使用axios請求攔截器和vuex設(shè)置全局loading方式

    Vue3使用axios請求攔截器和vuex設(shè)置全局loading方式

    本文介紹了如何在Vue.js項目中實現(xiàn)全局loading加載動畫,并通過axios攔截器控制loading的顯示與隱藏
    2025-12-12
  • vue中混入mixins的使用方法

    vue中混入mixins的使用方法

    mixins(混入)官方的描述是一種分發(fā) Vue 組件中可復(fù)用功能的非常靈活的方式,這篇文章主要給大家介紹了關(guān)于vue中混入 mixins使用的相關(guān)資料,需要的朋友可以參考下
    2021-10-10
  • rollup3.x+vue2打包組件的實現(xiàn)

    rollup3.x+vue2打包組件的實現(xiàn)

    本文主要介紹了rollup3.x+vue2打包組件的實現(xiàn),詳細的介紹了打包會存在的問題,包版本的問題,babel 轉(zhuǎn)換jsx等問題,具有一定的參考價值,感興趣的可以了解一下
    2023-03-03
  • 使用watch監(jiān)聽對象里面值的變化

    使用watch監(jiān)聽對象里面值的變化

    這篇文章主要介紹了使用watch監(jiān)聽對象里面值的變化,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • 深入探索VueJS Scoped CSS 實現(xiàn)原理

    深入探索VueJS Scoped CSS 實現(xiàn)原理

    這篇文章主要介紹了深入探索VueJS Scoped CSS 實現(xiàn)原理,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • vue攔截器及請求封裝代碼

    vue攔截器及請求封裝代碼

    這篇文章主要介紹了vue攔截器及請求封裝代碼,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • effect返回runner單測實現(xiàn)示例詳解

    effect返回runner單測實現(xiàn)示例詳解

    這篇文章主要為大家介紹了effect返回runner單測實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12

最新評論

吴旗县| 临猗县| 滦南县| 仙桃市| 苏尼特右旗| 德保县| 荣成市| 光山县| 扶绥县| 民县| 宁武县| 长垣县| 鄂托克旗| 澜沧| 沙洋县| 澳门| 洛扎县| 封开县| 大方县| 保定市| 大埔县| 贵南县| 普格县| 马龙县| 馆陶县| 竹溪县| 柳江县| 岗巴县| 商都县| 乐昌市| 陇南市| 绥化市| 南郑县| 常宁市| 师宗县| 廊坊市| 长沙县| 西充县| 罗江县| 越西县| 山东|