本節要實現的是導航標籤切換功能:html
html <div class="tabs"> <ul> <li class="is-active"><a>Pictures</a></li> <li><a>Music</a></li> <li><a>Videos</a></li> <li><a>Documents</a></li> </ul> </div>
首先來考慮標籤如何實現,咱們使用 name
屬性讓用戶定義標籤:git
<tab name="圖片"></tab> <tab name="音樂"></tab> <tab name="文檔"></tab> <tab name="視頻"></tab>
定義具體的 tab
組件:github
Vue.component('tab',{ template:` <div></div> `, mounted(){ console.log(this); } });
咱們打印出組件的對象,發現 name
的值並無傳遞進來:ide
其實就是以前講過的,組件的實例要傳遞數據給組件,必須在 props
中聲明:優化
Vue.component('tab',{ props:['name'], template:` <div></div> `, mounted(){ console.log(this); } });
如今,組件對象裏就能夠看到 name
傳遞進來了。ui
接下來是 zen-tabs
:this
Vue.component('zen-tabs',{ template:` <div><slot></slot></div> ` });
裏面定義了一個 slot
,以便用於自定義 tab
,好比:spa
<zen-tabs> <tab name="圖片"></tab> <tab name="音樂"></tab> <tab name="文檔"></tab> <tab name="視頻"></tab> </zen-tabs>
如今的問題是,zen-tabs
組件如何獲取 name
數據呢?咱們不妨打印出來看看:3d
Vue.component('zen-tabs',{ template:` <div><slot></slot></div> `, mounted(){ console.log(this); } });
效果以下:code
也就是說,若是一個組件(zen-tabs
,稱之爲父組件)裏面使用了另一個組件(tab
,稱之爲子組件),那麼能夠經過 $children
獲取子組件的數據。
Vue.component('zen-tabs',{ template:` <div><slot></slot></div> `, mounted(){ this.tabs = this.$children; }, data(){ return { tabs:[] } } });
如今,咱們將子組件的數據賦值給了 tabs
變量了,而後就可使用了:
Vue.component('zen-tabs',{ template:` <div> <div class="tabs"> <ul> <li v-for="tab in tabs"><a href="#">{{tab.name}}</a></li> </ul> </div> <div><slot></slot></div> </div> `, mounted(){ this.tabs = this.$children; }, data(){ return { tabs:[] } } });
效果以下:
接下來標籤的激活功能。首先,咱們爲第一個標籤添加激活功能看看:
<div id="root" class="container"> <zen-tabs> <tab name="圖片" selected="true"></tab> <tab name="音樂"></tab> <tab name="文檔"></tab> <tab name="視頻"></tab> </zen-tabs> </div>
tab
組件中在 props
中定義 selected
,並賦予默認值:
Vue.component('tab',{ props: { name:{require:true}, selected: {default:false} }, template:` <div></div> ` });
最後,能夠經過 selected
的值來決定是否添加激活類 is-active
:
Vue.component('zen-tabs',{ template:` <div> <div class="tabs"> <ul> <li v-for="tab in tabs" :class="{'is-active':tab.selected === true}"> <a href="#">{{tab.name}}</a> </li> </ul> </div> <div><slot></slot></div> </div> `, mounted(){ this.tabs = this.$children; }, data(){ return { tabs:[] } } });
發現沒效果:
這是由於,咱們使用的的是 selected = "true"
,這種寫法只能傳遞字面量,所以,傳遞的是字符串 "true"
,而咱們使用了 ===
來判斷傳入的究竟是不是布爾值 true
,結果就返回 false
了。
所以,若是要動態的傳遞屬性,須要使用:
<tab name="圖片" :selected="true"></tab>
這樣話 "true"
就被當成表達式來解析了,就爲布爾值 true
了。修改以後,效果就出來了:
接下來,就能夠根據用戶的點擊來動態切換標籤了:
component('zen-tabs',{ template:` <div> <div class="tabs"> <ul> <li v-for="tab in tabs" :class="{'is-active':tab.selected === true}" @click="selectTab(tab)"> <a href="#">{{tab.name}}</a> </li> </ul> </div> <div><slot></slot></div> </div> `, mounted(){ this.tabs = this.$children; }, data(){ return { tabs:[] } }, methods:{ selectTab(selectedTab){ this.tabs.forEach(function(tab){ tab.selected= (selectedTab.name == tab.name); }) } } });
這樣作,理論上是沒問題的,實際上,會報錯:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "selected"
爲何 Vue 不提倡這樣作,由於咱們在組件裏面修改 selected
的值,這樣可能會對外部形成影響,爲了保持鬆耦合,請將 props
僅僅當成是一種傳遞數據(而非改變數據)的方式。咱們能夠本身在內部定義變量:
Vue.component('tab',{ props: { name:{require:true}, selected: {default:false} }, template:` <div></div> `, mounted(){ this.isActive = this.selected; }, data(){ return { isActive:false } } }); Vue.component('zen-tabs',{ template:` <div> <div class="tabs"> <ul> <li v-for="tab in tabs" :class="{'is-active':tab.isActive=== true}" @click="selectTab(tab)"> <a href="#">{{tab.name}}</a> </li> </ul> </div> <div><slot></slot></div> </div> `, mounted(){ this.tabs = this.$children; }, data(){ return { tabs:[] } }, methods:{ selectTab(selectedTab){ this.tabs.forEach(function(tab){ tab.isActive= (selectedTab.name == tab.name); }) } } });
最後,優化一下該組件,首先是容許用戶自定義視圖:
<zen-tabs> <tab name="圖片" :selected="true">圖片視圖</tab> <tab name="音樂">音樂視圖</tab> <tab name="文檔">文檔視圖</tab> <tab name="視頻">視頻視圖</tab> </zen-tabs>
只須要稍微修改下 tab
的模板:
Vue.component('tab',{ template:` <div v-show="isActive"> <slot></slot> </div> `
最後是超連接功能,用計算屬性來實現:
Vue.component('tab',{ computed:{ href(){ return '#' + this.name.toLowerCase().replace(/ /g,'-'); } } }); Vue.component('zen-tabs',{ template:` <div> <div class="tabs"> <ul> <li v-for="tab in tabs" :class="{'is-active':tab.isActive=== true}" @click="selectTab(tab)"> <a :href="tab.href">{{tab.name}}</a> </li> </ul> </div> <div><slot></slot></div> </div> `,
經過計算屬性,讓超連接返回 #
+ 標籤名
的方式,若是標籤名中存在空格,就用 -
來代替。
附錄: