切換語言為:簡體

封裝 Tab 元件,在元件中使用插槽時遇到的問題

  • 爱糖宝
  • 2024-07-05
  • 2120
  • 0
  • 0

封裝 Tab 元件,在元件中使用插槽時遇到的問題

最近由於公司專案使用 vue3vant 重構,並且所有的頁面都需要按照設計圖來,在使用 vant 元件庫的 tabs 元件時,我發現元件的樣式和設計圖完全不一樣,並且如果要修改樣式,只能使用 :deep() 穿透來直接修改元件的樣式,考慮到 tabs 元件我這邊使用起來很簡單,只需要切換即可,所以決定自己寫一個簡單的,以下是 tabs 元件的程式碼:

tabs.vue

<template>
  <div>
    <render-tab-bar/>
    <render-content/>
  </div>
</template>
<script setup>
import {ref, useSlots, h} from 'vue'
const globalProps = defineProps({
  name: String,
  default: Number
})
const slots = useSlots()
let currentTab = ref(globalProps.default || 0)
const emit = defineEmits(['update:default'])

const dealClick = (tab) => {
  emit('update:default', tab)
  currentTab.value = tab
}

const renderOneButton = (name, tab, index) =>
    h(
    'label',
    {
      class: {
        'tab-bar-button-item': true,
        'tab-bar-button-item-active': currentTab.value === tab
      }
    },
    [
      h(
          'input',
          {
            style: {
              display: 'none'
            },
            type: 'radio',
            name: globalProps.name,
            value: name,
            onclick: () => dealClick(tab)
          },
          {}
      ),
      name
    ]
    )

const renderTabBar = () =>
    h(
        'div',
    {
      class: "tab-bar-button-list"
    },
        slots.default &&
        slots.default().map((item, index) => {
          return renderOneButton(item.props?.name, item.props?.tab, index)
        })
    )

const renderContent = () => {
  return (
      slots.default &&
      slots.default().find((item) => {
        if (currentTab.value === 0) {
          return true
        }
        return item.props?.tab === currentTab.value
      })
  )
}
</script>
<style scoped>
:deep(.tab-bar-button-list){
  font-size: 16px;
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-around;
  align-items: center;
  padding: 5px 0;
  margin: 10px 20px;
  border-radius: 90px;
  background-color: rgb(245, 247, 251);
  overflow-y: hidden;
  overflow-x: auto;
 .tab-bar-button-item {
    flex: 1;
    padding: 3px 0;
    text-align: center;
    border-radius: 40px;
  }

  .tab-bar-button-item-active {
    background: #FB4624;
    color: white;
    transition: all 0.3s;
  }
}

</style>

tab.vue

<template>
  <div>
    <slot></slot>
  </div>
</template>

程式碼很簡單,tabs元件接收兩個props屬性,nameinput單選框的屬性,default 代表當前選中的是哪一個子tabtab 也接受兩個props屬性,nameinputvalue值,也就是要在頂部顯示的文字,tab屬性是標記當前tab元件的唯一標識,用來和 tabs元件的 default 屬性判斷是否相同,相同則新增被選中的樣式。

index.vue 中使用

<template>
  <div class='content'>
   <Tabs :default=1>
      <tab name='第一頁' :tab=1>
        測試1
      </tab>
      <tab name='第二頁' :tab=2>
        測試2
      </tab>
    </Tabs>
  </div>
</template>

<script setup>
import Tabs from '@/components/Tabs/tabs.vue'
import Tab from '@/components/Tabs/tab.vue'

</script>

<style  scoped>
.content{
  font-size: 20px;
}
</style>

封裝 Tab 元件,在元件中使用插槽時遇到的問題

可以看到,實現了正常的切換,另外,tab 插槽內使用元件也可以正常生效。

demo-child.vue

<script setup>
defineProps({
  msg: String
})
</script>

<template>
  <div>{{msg}}</div>
</template>

<style scoped>

</style>

index.vue 中使用

<template>
  <div class='content'>
    <Tabs :default=1>
      <tab name='第一頁' :tab=1 >
        <DemoChild msg='元件測試1'></DemoChild>
      </tab>
      <tab name='第二頁' :tab=2>
        <DemoChild msg='元件測試2'></DemoChild>
      </tab>
    </Tabs>
  </div>
</template>

<script setup>
import Tabs from '@/components/Tabs/tabs.vue'
import Tab from '@/components/Tabs/tab.vue'
import DemoChild from './demo-child.vue'
</script>

<style  scoped>
.content{
  font-size: 20px;
}
</style>

封裝 Tab 元件,在元件中使用插槽時遇到的問題

但是,當我在demo-child 元件中使用插槽,將另一個元件放在 demo-child 的插槽內時,切換就失效了。

demo-child.vue 稍作修改

<script setup>
defineProps({
  msg: String
})
</script>

<template>
  <div>{{msg}}</div>
  <div style='color:red;'>
    <slot name='top'></slot>
  </div>
  <div style='color:green;'>
    <slot name='bottom'></slot>
  </div>
</template>

<style scoped>

</style>

demo-Grandson.vue

<script setup>
defineProps({
  GrandMsg: String
})
</script>

<template>
  <div>{{GrandMsg}}</div>
</template>

<style scoped>

</style>

index.vue 中使用

<template>
  <div class='content'>
    <Tabs :default=1>
      <tab name='第一頁' :tab=1>
        <DemoChild msg='測試1'>
          <template #top>
            <DemoGrandson  GrandMsg='測試1的插槽放的元件'></DemoGrandson>
          </template>
        </DemoChild>
      </tab>
      <tab name='第二頁' :tab=2>
        <DemoChild msg='測試2'>
          <template #bottom>
            <DemoGrandson  GrandMsg='測試2的插槽放的元件'></DemoGrandson>
          </template>
        </DemoChild>
      </tab>
    </Tabs>
  </div>
</template>

<script setup>
import Tabs from '@/components/Tabs/tabs.vue'
import Tab from '@/components/Tabs/tab.vue'
import DemoChild from './demo-child.vue'
import DemoGrandson from "./demo-grandson.vue"
</script>

<style  scoped>
.content{
  font-size: 20px;
}
</style>

封裝 Tab 元件,在元件中使用插槽時遇到的問題

封裝 Tab 元件,在元件中使用插槽時遇到的問題 這是為什麼呢,觀察切換時的 dom 結構即可發現,實際上切換時,只有一個 div 發生了改變,也就是說,實際上元件並沒有進行銷燬和重新掛載,而是進行了複用,這是因為 Vue 3 使用虛擬DOM來最佳化dom 的更新。當元件的資料變化時,Vue 會先更新虛擬 dom 樹,然後透過 diff 演算法找出最小必要變更,並應用這些變更到真實 dom 上。Vue 會盡量複用現有的元件例項和 dom 元素,以提高效能。如果兩個元件具有相同的型別和相似的 props/stateVue 可能會複用這些元件的例項,而不是銷燬舊例項並建立新例項。解決方式也很簡單,只要給元件一個 key值,使元件更準確地識別哪些元素應該被保留、複用、移動或刪除即可。

修改後的程式碼:

index.vue

<template>
  <div class='content'>
    <Tabs :default=1>
      <tab name='第一頁' :tab=1>
        <DemoChild msg='測試1' :key='1'>
          <template #top>
            <DemoGrandson :key='1' GrandMsg='測試1的插槽放的元件'></DemoGrandson>
          </template>
        </DemoChild>
      </tab>
      <tab name='第二頁' :tab=2>
        <DemoChild msg='測試2' :key='2'>
          <template #bottom>
            <DemoGrandson :key='2' GrandMsg='測試2的插槽放的元件'></DemoGrandson>
          </template>
        </DemoChild>
      </tab>
    </Tabs>
  </div>
</template>

<script setup>
import Tabs from '@/components/Tabs/tabs.vue'
import Tab from '@/components/Tabs/tab.vue'
import DemoChild from './demo-child.vue'
import DemoGrandson from "./demo-grandson.vue"
</script>

<style  scoped>
.content{
  font-size: 20px;
}
</style>

封裝 Tab 元件,在元件中使用插槽時遇到的問題

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.