《Vue.js實戰》章七 組件——標簽頁組件:思路詳解


先上帶有部分注釋的全部代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="Vue.2.6.10.js"></script>
</head>
<body>
    <!-- 需求分析: 1.每個標簽頁的主體內容(即html結構、文本、圖片等)應當是由使用組件的父級控制的,這部分可以作為一個slot, slot的數量決定了標簽切換按鈕的數量。點擊每個按鈕時,另外的標簽對應的slot應該被隱藏掉。第一種想法,在slot里寫三個div,再 根據需要隱藏和顯示。更好的辦法應該是讓組件幫忙處理這部分邏輯,我們只需要聚焦slot內容本身。這種情況下,再定義一個子組件pane, 嵌套在標簽頁組件tabs里,把業務代碼放在pane的slot內,然后各個pane都作為整個tabs的組件。 tabs和pane兩個組件是分離的,但tabs上的標題應該由pane組件來定義,又因為slot寫在pane里,所以應該在組件初始化(或標簽標題動態改變)時,tabs從 當前的pane里獲取標題並保存起來。-->
    
    <div id="app" v-cloak>
        <tabs v-model="activeKey">
            <pane label="標簽一" name="1"> 標簽一內容 </pane>
            <pane label="標簽二" name="2"> 標簽二內容 </pane>
            <pane label="標簽三" name="3"> 標簽三內容 </pane>
        </tabs>
    </div>
<script> Vue.component('pane',{ name:"pane", template:'\ <div class="pane" v-show="show">\ <slot></slot>\ </div>', data() { return { show:true//pane需要控制標簽頁內容的顯示與隱藏,配合v-show
                    //點擊到這個pane對應的標簽頁按鈕時,這個pane的show值才設置為true
                    //既然這樣的話就應該有唯一標識的值來標識這個pane,可以設置一個prop:name讓用戶來設置,但不是必必需的,默認從0開始
                    //這一步操作由pane執行,pane本身並不知道自己是第幾個。
                    //還有prop:label,tabs組件需要將它顯示在標簽欄標題中
 } }, props:{ name:{ type:String }, label:{ type:String, default:'' } },//prop:label是用戶可以動態調整的,所以在pane初始化、label更新時都需要通知父組件也更新,
            //可以直接通過this.$parent來訪問tabs組件的實例來調用它的方法更新標題
 methods: { updateNav(){ this.$parent.updateNav();//調用tabs的方法updateNav
 } }, watch: { label(){ this.updateNav(); } }, mounted() { this.updateNav();//在pane初始化時調用一遍tabs的方法updateNav,同時監聽prop:label,在其更新時也調用
 }, }) Vue.component('tabs',{ template:'\ <div class="tabs">\ <div class="tabs-bar">\ <!--標簽頁的標題,需要使用v-for-->\ <div \ :class="tabCls(item)"\ v-for="(item,index) in navList"\ @click="handleChange(index)">\ {{ item.label }}\ </div>\ </div>\ <div class="tabs-content">\ <!--這里的slot即是嵌套的pane組件-->\ <slot></slot>\ </div>\ </div>', props:{ value:{ type:[String,Number] } }, data() { return { //由於不能修改value於是自己復制一份維護
 currentValue:this.value, //用於渲染tabs的標題
 navList:[] } }, methods:{ tabCls:function(item){ return [ 'tabs-tab', { //給當前選中的div加一個class
                            'tabs-tab-active':item.name === this.currentValue } ] }, //點擊tab標題時觸發
 handleChange:function(index){ var nav = this.navList[index]; var name = nav.name; //改變當前選中的tab,並觸發下面的watch
                    this.currentValue = name; //更新value
                    this.$emit('input',name); //觸發一個自定義事件供父級使用
                    this.$emit('on-click',name); }, getTabs(){ //通過遍歷子組件得到所有panes組件
                    return this.$children.filter(function(item){ return item.$options.name === 'pane' }) }, updateNav(){ this.navList = []; //設置對this的引用,在function回調里this指向的並不是Vue實例
                    var _this = this; this.getTabs().forEach(function(pane,index){ _this.navList.push({ label:pane.label, name:pane.name || index }); //如果沒有給pane設置name就默認設置為索引
                        if(!pane.name){ pane.name = index; }//設置當前選中的tabs的索引
                        if(index === 0){ if(!_this.currentValue){ _this.currentValue = pane.name || index; } } }); this.updateStatus(); }, updateStatus(){ var tabs = this.getTabs(); //再次遍歷pane組件,不過這是為了將當前選中的
                    //tab對應的pane組件內容顯示出來,並將沒有選中的隱藏掉
                    //在上一步中可能需要我們來設置currentValue來標識當前選中項的name(只有在用戶沒有設置value時才會自動設置)
                    var _this = this; //顯示當前選中的tab對應的pane組件,隱藏沒有選中的
 tabs.forEach(function(tab){ return tab.show = tab.name === _this.currentValue; // return tab.show = (tab.name === _this.currentValue);
 }) } }, watch:{ value:function(val){ this.currentValue = val; }, currentValue:function(){ //在當前選中的tabs發生變化時更新pane顯示狀態
                    this.updateStatus(); } } }); var app = new Vue({ el:"#app", data:{ activeKey:'1' } }); </script>
</body>
</html>

先來明確思路:

務必明確插槽slot的作用與組件渲染方式。

 

以下為pane組件(建議將每一點結合起來看)

1.name:'pane',這里的name此時不是為了在組件內部遞歸調用自身,而是作為唯一標識,既可以專門設置一個prop:name,也可以不做設置在后面用index代替

2.show:true,由pane配合v-show以及name控制標簽頁的顯示與隱藏,注意這一步操作是由tabs組件來進行的。

3.props的傳入過濾,用戶是可以動態調整prop:label的!因此如果在pane初始化或label更新時都需要通知父組件更新狀態(即導航欄)

4.子組件的updateNav實際上是調用了父組件的這個同名方法

5.同4,watch與mounted中的updateNav方法也是調用父組件,作用域亦在父組件。

 

以下為tabs組件,分析並不從上到下。

6.先來看上面的updateNav()方法到底做了什么?

先將tabs的子組件列表navList初始化,然后調用getTabs()方法,得到所有的pane組件,注意filter、$options。

然后將組件的label值與name推入數組,如果我們沒有給出name(如下圖),那么就以索引值作為name。

在遍歷過程中,遍歷到第一個組件時,才會觸發if(index === 0){...},此時這里的index是作為參數傳入的索引值(無論有無指定name),

如果沒有指定currentValue,那么此時將currentValue指定為這個pane的name或索引值。

再結束前還會觸發一次updateStatus方法,見下文。

7.currentValue是什么??首先,我們知道這個變量存儲着當前顯示的標簽頁,上一點的操作實際上就是確保在初始化時有一個默認標簽頁顯示。其他暫且不管。

.

8.先來看看模板中的這些是什么意思:

首先得明確v-for在這里會生成navList中的各個pane組件,並為每一個div填充這些數據。

tabCls的返回值是一個字符串數組,必定包含tabs-tab,如果這個標簽頁當前是被選中的,那么tabs的currentValue值即等於當前標簽頁的name,還會多獲得一個active的類名。

9.當點擊這個標簽時,觸發handleChange函數,並傳入當前這個標簽的index值,在這個函數里會完成以下操作:

  1.先將當前標簽以及name屬性存儲起來(nav,name)

  2.將tabs組件的currentValue修改為name值

  3.觸發父組件的input事件與on-click自定義事件,

我們都知道v-model的本質是一個語法糖,activeKey的值會被動態修改為name值。(activeKey存在於tabs的data中,這一修改是雙向的,便於進行其他操作)。

10.

updateStatus()方法,實際上是負責控制標簽頁顯示與隱藏的方法。

再次遍歷獲取pane組件,並將結果再次遍歷(注意,兩次遍歷分別的對象),如果tab.name === _this.currentValue,就可以露臉,否則隱藏。

這一步中實際依據是否設置value有不同的處理結果,有可能只規定了value的類型而沒有規定default,currentValue為null。

注意!可能會都感到疑惑這個value的值在哪?

事實上value值就是那個activeKey,還記得v-model這個語法糖嗎?

注意v-bind:value=“dataA”這一行!!!!

緊接着由於沒有指定value,currentValue會被自動設置(第六點),為1(因為本例中有給prop指定name,否則就是第一個索引值0)。

如果指定了value值(本例value值即為activeKey),即指定要求初始化時被選中的標簽頁,那么pane初始化時,currentValue被指定為value值(以3為例),在tabs的updateNav()方法中,不會執行if(index === 0){...}中的改變,緊接着執行updateStatus(),name為3的pane組件會被顯示出來。

 

12.假定一個完整的執行流程:

初始activeKey=2——v-model=2——value=2——cV=2——初始化,updateNav()——填充navList——執行updateStatus,將第二個組件顯示出來。

點擊第一個標簽頁——觸發handleChange,改變cV值,更新value——tabs中的watch監控到value與cV值變化,將cV值修改為value值,同時再次執行updateStatus,更改顯示狀態。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM