[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來讓父元件使用,這也算是我沒有想過可以這樣做的一個方式,有任何想法再請指教囉。