[vue]自己寫一個tab的元件,來深入了解slot的運用

  • 4846
  • 0
  • vue
  • 2017-10-20

[vue]自己寫一個tab的元件,來深入了解slot的運用

前言

前陣子有需要去自訂一個跟原網站幾乎一模一樣的tab元件,所以便開始研究如何刻一個好用的tab元件,不研究也就算了,一研究起來發覺還真不是那麼好搞,只好去看一些第三方人家是怎麼寫的,然後自己用比較簡單的方式來刻一個符合公司用的,不需要用到別人那麼複雜的元件,而經由刻一個tab元件,來記錄一下自己比較不熟悉的運用方式,接下來就開始記錄一些筆記吧。

簡單了解一下何謂slot

首先來看一下官方有關slot的部份https://vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots

簡單來說當我們在建立一個component的時候,我們通常在中間區塊沒有放任何內容

parent

<template>
  <div>
    <children></children>
  </div>
</template>

<script>
import Children from './components/Children.vue'

export default {
  name: 'parent',
  components: {
    Children
  }
}
</script>

<style>

</style>


children

<template>
  <div>
    this is child component
  </div>
</template>

<script>
export default {
  name: 'children'
}
</script>

<style>
</style>

這是寫死的方式,但是如果我們希望可以替換掉children的某些內容,我們就可以使用slot的方式來做。

parent

<template>
  <div>
    <children>
      parent replace children content
    </children>
  </div>
</template>

children

<template>
  <div>
    <!-- 增加slot去包住我們想要取代的區塊 -->
    <slot>
    this is child component
    </slot>
  </div>
</template>

這樣子中間的內容就會替換成parent的文字了,所以我們只要在使用的元件裡面,把我們想要改成調用端來決定內容的時候,我們就可以用slot包裝起來,那預設就會是子元件裡面的內容,但我們又可以彈性的從父元件去取代掉我們所調用元件的內容。

使用slot的特性來完成tab的元件

首先來看一下完成後的tab調用的原始碼,還有demo的效果

 <tabs v-model="model">
      <tab name="First tab" label="First tab">
        This is the content of the first tab
      </tab>
      <tab name="Second tab" label="Second tab">
        This is the content of the second tab
      </tab>
      <tab name="Custom fragment" label="Custom fragment">
        The fragment that is appended to the url can be customized
      </tab>
      <tab name="Prefix and suffix" label="Prefix and suffix">
        A prefix and a suffix can be added
      </tab>
    </tabs>

而v-model會取到的則是name的值

讓我們來看一下這個tabs的原始碼是長什麼樣子吧

tabs

<template>
  <div class="tabs-component">
    <ul role="tablist" class="tabs-component-tabs">
      <li v-for="(tab,index) in tabs" :key="index" :class="{'is-active':tab.name===value}" class="tabs-component-tab">
      <!-- 產出頁籤,而tab.name是從調用端傳給tab,再取this.$children來取得裡面的props -->
        <a @click="selectTab(tab.name)" class="tabs-component-tab-a">{{tab.label}}</a>
      </li>
    </ul>
    <div class="tabs-component-panels">
      <!-- 產出內容區塊,如果這邊沒有slot的話,就無法取得children的值了 -->
      <slot/>
    </div>
  </div>
</template>

<script>
export default {
  props: ['value'],
  data () {
    return {
      tabs: []
    }
  },
  methods: {
    selectTab (tabName) {
      this.$emit('input', tabName)
    }
  },
  created () {
    // tabs是從子元件取得的,也就是tab的部份,可以取得子元件的vue內容
    this.tabs = this.$children
  }
}
</script>

<style scoped>
.tabs-component {
  margin: 4em 0;
}

.tabs-component-tabs {
  border: solid 1px #ddd;
  border-radius: 6px;
  list-style: none;
  margin-bottom: 5px;
}

@media (min-width: 700px) {
  .tabs-component-tabs {
    border: 0;
    align-items: stretch;
    display: flex;
    justify-content: flex-start;
    margin-bottom: -1px;
  }
}

.tabs-component-tab {
  color: #999;
  font-size: 14px;
  font-weight: 600;
  margin-right: 0;
}

.tabs-component-tab:not(:last-child) {
  border-bottom: dotted 1px #ddd;
}

.tabs-component-tab:hover {
  color: #666;
}

.tabs-component-tab.is-active {
  color: #000;
}

@media (min-width: 700px) {
  .tabs-component-tab {
    background-color: #fff;
    border: solid 1px #ddd;
    border-radius: 3px 3px 0 0;
    margin-right: .5em;
    transform: translateY(2px);
    transition: transform .3s ease;
  }

  .tabs-component-tab.is-active {
    border-bottom: solid 1px #fff;
    z-index: 2;
    transform: translateY(0);
  }
}

.tabs-component-tab-a {
  align-items: center;
  color: inherit;
  display: flex;
  padding: .75em 1em;
  text-decoration: none;
  cursor: pointer;
}

.tabs-component-panels {
  padding: 4em 0;
}

@media (min-width: 700px) {
  .tabs-component-panels {
    border-top-left-radius: 0;
    background-color: #fff;
    border: solid 1px #ddd;
    border-radius: 0 6px 6px 6px;
    box-shadow: 0 0 10px rgba(0, 0, 0, .05);
    padding: 4em 2em;
  }
}
</style>

tab

<template>
  <section v-show="isActive" class="tabs-component-panel">
    <!-- 放置內容區塊 -->
    <slot />
  </section>
</template>

<script>
export default {
  props: {
    name: { required: true },
    label: { required: true }
  },
  computed: {
    isActive () {
      // this.$parent.value取得tabs裡面value的值
      return this.name === this.$parent.value
    }
  }
}
</script>

<style>

</style>

我覺得比較難懂的部份,全部寫在原始碼裡面了,簡單做個總結,我們在tabs放了一個slot,這個slot放的是tab的元件,tab又放了slot並傳進了props,所以我們又可以在tab裡面放進文字內容,最後我們在父元件去取得tab的props來讓父元件使用,這也算是我沒有想過可以這樣做的一個方式,有任何想法再請指教囉。