從零開始實現一個Vue級聯組件

從零開始實現一個Vue級聯組件

本文實現級聯組件須要用到自定義指令組件通訊相關知識,最好先閱讀如下兩篇文章:vue

Vue自定義指令json

Vue組件基礎與通訊segmentfault

1、組件簡介

本文實現的是一個省、市、縣...多級聯動組件,當組件渲染完成後默認會加載出全部的省名稱,當用戶點擊某個省的名稱後,右邊會自動添加一列顯示該省下對應的市名稱列表,當用戶點擊某個市後,右邊又會自動添加一列顯示該市下對應的縣名稱列表,同時支持級聯列表的打開和關閉。
圖片描述數組

2、組件實現設計思路

① 組件所須要的數據,數據結構很是簡單,對象裏面只有兩個屬性,一個是label(標籤名),若是當前標籤下還有子標籤,則會多一個children屬性,children屬性值爲一個數組每一個數組元素爲其下的一個子標籤數據結構

// data.json, 爲避免數據佔用太多篇幅,這裏只列舉了一條數據ide

[
    {
        "label": "江西",
        "children": [
            {
                "label": "贛州",
                "children": [
                    {
                        "label": "全南縣"
                    },
                    {
                        "label": "龍南縣"
                    }
                ]
            }
        ]
    }
]

② 咱們的級聯組件分爲上下兩部分組件,上部分顯示用戶選擇的路徑,下部分顯示用戶選擇列表,同時支持點擊級聯組件的上部分能夠實現下半部分的打開和關閉,點擊組件外面關閉組件的下半部分,這裏須要用到v-click-outside指令,這裏自定義指令的代碼就再也不重複,請參考Vue自定義指令
// Cascader.vue 新建一個Cascader.vue組件函數

<template>
    <div class="cascader" v-click-outside="close"> <!--實現點擊組件外面關閉組件下半部分-->
        <div class="title" @click="toggle">{{resultPath}}</div> <!--點擊上半部分能夠實現下部分的顯示和隱藏切換-->
        <div class="content" v-if="isVisible">
            <!--組件下半部分,即選擇列表部分-->
        </div>
    </div>
</template>

<script>
import clickOutside from "./../directives/clickOutside";
export default {
    name: "Cascader",
    directives: { // 在當前組件上註冊clickOutside指令
        clickOutside
    },
    props: ["options"], //定義一個options屬性用於接收外部傳遞給級聯組件的數據,即選擇項列表
    data() {
        return {
            isVisible: false,
            selectedItems: [] // 用戶已選擇項
        }
    },
    computed: {
        resultPath() { // 經過用戶已選擇項計算出用戶的選擇路徑
            return this.selectedItems.map((item) => item.label).join("/");
        }
    },
    methods: {
        close() { // 關閉下半部分(選擇列表部分)
            this.isVisible = false;
        },
        toggle() { // 下半部分(選擇列表部分)顯示和隱藏的切換
            this.isVisible = !this.isVisible;
        }
    }
}
</script>
注意到組件中有一個 selectedItems數據,這是一個 數組,默認值爲 空數組,由於當級聯組件渲染完成後,默認用戶是沒有點擊選擇其中任何一項的,只有當 用戶點擊了某一項後,纔會將 點擊的這一項添加到selectedItems數組中,其就是 記錄用戶的選擇項。這裏須要理解清楚選擇項的概念:
好比咱們的級聯組件有三列, 省、市、縣三列,結合上面的數據結構,整個省是一個大對象,即 省對象,省對象中有children屬性,裏面包括多個子對象,即 市對象,市對象中又包括children屬性,裏面包括多個子對象,即 縣對象,縣對象中再也不有children了,具體表示就是:
省對象:
{ "label": "江西", children: [省略...]}
市對象:
{ "label": "贛州", children: [省略...]}
縣對象:
{ "label": "全南縣"}
當用戶點擊 第一列,那麼就 將整個省對象添加到selectedItems數組中的 第一項位置,當用戶接着點擊了 第二列,如省對象中的label爲"贛州"的市對象,則 將整個市對象添加到selectedItems數組中的 第二項位置,當用戶又點擊了 第三列,如"贛州"市對象下的label爲"全南縣"的縣對象,則 將整個縣對象添加到selectedItems數組中的 第三項位置,這樣selectedItems數組中就保存了用戶選擇的三列數據了,而後 將三列數據中的label取出經過"/"鏈接起來,即用戶的選擇路徑"江西/贛州/全南縣"。

③ 接下來就是考慮組件拿到數據後,如何渲染的問題了
這裏須要用到組件內遞歸組件,咱們能夠左右兩列抽象成一個單獨的組件CascaderItem.vue,可是右邊這一列會不會顯示,得看用戶有沒有選擇左邊的項,若是點擊了左邊的項則顯示右邊的列,若是沒有點擊左邊的項則不顯示右邊的列。this

仍是以省、市、縣三列爲例, 中間的市這一列,既是省的右列,也是縣的左列,咱們已經將左右兩列抽象了一個單獨的CascaderItem組件,關鍵是理解 省這一列的右邊部分究竟是什麼?,從表面上看,省這一列的右邊就是一個市列,可是若是右邊僅僅是市這一列的話,那麼 當用戶點擊市這一列中的某項的時候,就沒法顯示市右邊的縣列了,因此 省這一列的右邊其實又是一個CascaderItem組件,只有這樣點擊市列中的某一項的時候,其右邊的縣列纔會顯示出來。因此咱們須要 在CascaderItem組件內遞歸本身,而組件內遞歸本身,那麼 必須給組件添加name屬性,即 給組件取一個名字,如:

// CascaderItem.vuespa

<template>
    <div class="cascader-item">
        <!--首先渲染出級聯組件的最左邊部分-->
        <div class="content-left">
                <div v-for="(item, index) in options" :key="index">
                    <div class="label" @click="select(item)"> {{item.label}}</div>
                </div>
        </div>
        <!--點擊左邊中的某個選項後,lists纔會有值纔會渲染右邊部分,一樣渲染右邊部分的時候,也是先渲染左邊部分-->
        <div class="content-right" v-if="lists && lists.length">
                <CascaderItem :options="lists" :selectedItems="selectedItems" :level="level + 1" @change="change"></CascaderItem>
        </div>
    </div>
</template>

<script>
export default {
    name: "CascaderItem", // 給組件起個名字,方便組件內部遞歸調用,即組件內部本身調用本身
    props: ["options", "selectedItems", "level"],
    computed: {
        lists() {
            // 根據內容value的變化顯示列表,根據當前點擊位置對應的level去獲取要顯示的列表
            return this.selectedItems[this.level] && this.selectedItems[this.level].children;
        }
    },
}
</script>
CascaderItem組件組件的渲染數據來自於頂層父組件Cascader中的selectedItems數據,由於用戶點擊了左側列中的項後, 會將點擊的item項添加到selectedItems中,selectedItems中數據變化以後纔會顯示右側的列。
CascaderItem組件須要接收一個 level屬性,用來記錄當前CascaderItem組件所屬層級,即第幾列,爲了方便,咱們 從0開始表示第一列,即第一層因此Cascader.vue中level傳入0, 後面沒加一層level會加1,如:

// 補全上面的Cascader.vue,渲染出下半部分設計

<template>
    <div class="cascader" v-click-outside="close">
        <div class="title" @click="toggle">{{resultPath}}</div>
        <div class="content" v-if="isVisible">
            <!--將左右兩部分封裝爲一個組件,而後循環輸出組件-->
            <CascaderItem :options="options" :selectedItems="selectedItems" :level="0" @change="change"></CascaderItem><!--傳入level從0開始-->
        </div>
    </div>
</template>
CascaderItem組件的左邊部分都監聽了一個click事件,當用戶點擊左邊的列選項後,須要 將當前所在level和item對象數據傳遞到Cascader父組件中的selectedItems數組中,以便獲取用戶的選擇路徑,由於 單向數據流,子組件不能直接修改父組件傳遞過來的數據,因此須要 去父組件中修改數據,這裏 以事件的方式通知頂層父組件本身更新數據

// CascaderItem.vue給CascaderItem組件添加一個select()方法

export default {
    methods: {
        select(item) { // 處理CascaderItem組件內左側列點擊事件,item爲當前點擊的對象
            // 向上一級發射一個change事件,通知上層進行修改,並將當前點擊的層級level和item傳遞過去
            this.$emit("change", {level: this.level, item: item});
        }
    }
}
因爲CascaderItem是遞歸調用的,因此如今的組件調用關係爲: Cascader --> CascaderItem --> CascaderItem --> CascaderItem --> ......
頂層父組件爲Cascader,因此 CascaderItem也多是CascaderItem的父組件CascaderItem組件自身也須要監聽change事件,主要就是負責將數據改變信號傳遞到Cascader頂層父組件上,如:

// CascaderItem.vue給CascaderItem組件添加一個change事件處理方法

export default {
    methods: {
        change(newValue) { // 向頂層傳遞數據改變信息
            this.$emit("change", newValue);
        }
    }
}
頂層父組件Cascader接收到數據改變信號後,就須要改變selectedItems數據了,即將用戶的選擇項添加到對應的位置,如:

// Cascader.vue 添加change事件處理函數

export default {
    methods: {
        change(newValue) {
            this.selectedItems.splice(newValue.level, 1, newValue.item); // 替換當前點擊位置信息
            this.selectedItems.splice(newValue.level + 1); // 刪除當前點擊位置以後的數據
        }
    }
}
Cascader組件除了替換掉指定level中的數據外, 還須要將當前level以後的數據刪除掉,不然當前level以後的數據還在,致使右側路徑仍然保留而顯示不一致。

至此,一個簡單的級聯組件就實現了,能夠在App.vue中直接使用,如:
// App.vue

<template>
  <div>
      <Cascader :options="options"></Cascader> <!--直接將數據傳遞給級聯組件便可-->
  </div>
</template>
<script>
import Cascader from "./components/Cascader";
import dataList from "./data/data.json";
export default {
    components: {
        Cascader
    },
    data() {
      return {
        options: dataList
      }
    }
}    
</script>

3、總結

整個Cascader組件設計思路就是: 在頂層父組件Cascader中 添加一個selectedItems數組,用於保存用戶點擊的 level層級(列序號)和對應的 item對象,同時用於 生成用戶的選擇路徑,當用戶點擊了CascaderItem組件的左側列中某項後, 經過層層傳遞事件的方式通知頂層父組件Cascader對其數據進行更新,頂層父組件Cascader更新數據後,CascaderItem組件 從selectedItems中取出對應level的item對象,而後獲取item的children並遍歷顯示右側列
相關文章
相關標籤/搜索