Vue 2.0 入門系列(8)組件實例之導航標籤

效果

本節要實現的是導航標籤切換功能:html

clipboard.png

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

clipboard.png

其實就是以前講過的,組件的實例要傳遞數據給組件,必須在 props 中聲明:優化

Vue.component('tab',{
    props:['name'],
    template:`
        <div></div>
    `,
    mounted(){
        console.log(this);
    }
});

如今,組件對象裏就能夠看到 name 傳遞進來了。ui

clipboard.png

接下來是 zen-tabsthis

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

clipboard.png

也就是說,若是一個組件(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:[]
        }
    }
});

效果以下:

clipboard.png

接下來標籤的激活功能。首先,咱們爲第一個標籤添加激活功能看看:

<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:[]
        }
    }
});

發現沒效果:

clipboard.png

這是由於,咱們使用的的是 selected = "true",這種寫法只能傳遞字面量,所以,傳遞的是字符串 "true",而咱們使用了 === 來判斷傳入的究竟是不是布爾值 true,結果就返回 false 了。

所以,若是要動態的傳遞屬性,須要使用:

<tab name="圖片" :selected="true"></tab>

這樣話 "true" 就被當成表達式來解析了,就爲布爾值 true 了。修改以後,效果就出來了:

clipboard.png

接下來,就能夠根據用戶的點擊來動態切換標籤了:

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>    
    `,

經過計算屬性,讓超連接返回 # + 標籤名 的方式,若是標籤名中存在空格,就用 - 來代替。


附錄:

相關文章
相關標籤/搜索