1. 程式人生 > >Vue非典型封裝Bootstrap-Select公共元件(非同步獲取資料,prop自定義函式)

Vue非典型封裝Bootstrap-Select公共元件(非同步獲取資料,prop自定義函式)

本文重點討論的問題:

1. 如何統一所有例項資料,而不是例項化元件時傳入資料。並非提倡這種做法,結合實際需求。
2. 如何prop元件例項的自定義函式。

以上問題比較鮮見,於是把我的解決思路寫下來跟各位分享。完整具體的實現程式碼就不列出了。

接觸VUE時間不長,逐漸瞭解到它的強大元件功能後,就開始著手將專案中的可以複用的選擇框封裝成公共元件。
開始的選擇並非是Bootstrap-select外掛,而是html標準的標籤。結合自己的實際需求,決定採用全域性元件。遇到的第一個問題便是資料與元件的關係。我們先來看VUE官方文件對元件Data的定義:

data 必須是函式 構造 Vue 例項時傳入的各種選項大多數都可以在元件裡使用。只有一個例外:data 必須是函式。

理解起來應該不難,為了避免元件例項之間的資料影響,這麼做是正確的。但這個做法跟我的實際需求產生了矛盾,我希望我的所有的元件例項都是公用同一套選項資料,資料非同步從服務端獲取。實際上專案中也確是如此。於是暫定為每個例項定義自己的資料,後期考慮資料從全域性變數(字典快取)獲取。

data:function(){
            return {
                flg:false,
                loading: false,
                map:new HashMap(),
                list1:[],
                list2:[],
                ...
} }

增加的loading標識是為了良好的使用者體驗,在等待非同步獲取資料期間告知使用者載入中。

template: '<div class="col-sm-8" v-if="loading">'
         +'</div>'
         +'<div class="col-sm-8" v-else>'
                +'載入中...'
         +'<div/>'

由於我的元件是二級聯動下來列表,於是利用map快取二者的資料關係,減少後端互動,為了相容非ES6標準,自己實現了hashMap。我們把一級的value作為key,對應二級的list作為value儲存。

接下來的問題是非同步得到的資料交與vue渲染,模版和程式碼片段如下:

'<select class="form-control" v-model="list1Select">'
+'<option v-for="(e, index) in list1" :value="e.value">{{e.name}}</option>'
+'</select>'
created:function(){
    this.setData();
},
methods : {
            setData:function(){
                $.ajax({
                    ...
                    success:function(r){
                        this.loading = true
                        this.list1 = r.list
                        ...
                        this.$nextTick(function(){
                            ...
                        }
                    }.bind(this)
                    //切換this指向,使其指向元件的例項
                });
            },
            ...

需要監聽一級下拉列表:

watch:{
        list1Select:function(){
            if(this.list1Select){
                var list = this.map.get(this.list1Select);
                if(!list){
                    this.queryList2ByList1();
                }else{
                    this.list2 = list;
                }
            }
         }
      },

至此簡單的封裝結束。
原生的控制元件功能簡單,那麼如何進化到Boostrap-select呢?
需要注意的是,我們通過Vue渲染的<option/>,Boostrap-select需要再次渲染。這個問題網上的普遍解決辦法是自己根據資料生成<option/>內容再append。

正確的使用姿勢應該是vm.$nextTick( [callback] )。
我們來看官方文件:

引數:
{Function} [callback]
用法:
將回調延遲到下次 DOM 更新迴圈之後執行。在修改資料之後立即使用它,然後等待 DOM 更新。它跟全域性方法 Vue.nextTick
一樣,不同的是回撥的 this 自動繫結到呼叫它的例項上。

於是事情變得簡單明瞭,程式碼片段:

setData:function(){
            $.ajax({
                ...
                success:function(r){
                    this.loading = true
                    this.list1 = r.list
                    //儲存至map
                    ...
                    this.$nextTick(function(){
                       $('#list1Select').selectpicker('refresh');
                    }
                }.bind(this)
            });
        },

如此我們來更進一步,

通過props在例項化元件時定製功能。

例如常見的boostrap-select的dataLiveSearch。程式碼片段

<select class="form-control selectpicker" title="請選擇" :data-live-search="dataLiveSearch" >
props : ['dataLiveSearch']

我們來逐漸演進,在例項化元件後,執行自定義指令碼來選擇下拉列表內容。

轉換思路,如何通過props傳入一個自定義函式?

實際上我遇到的需求便是在例項化元件時需要自行決定選中哪一項。於是決定prop傳入這個自定義函式。

props片段:

props : ['dataLiveSearch','loadSelectFunc']

關鍵程式碼:

this.$nextTick(function(){
    $('#list1Select').selectpicker('refresh');
    var _fun = window[this.$options.propsData.loadSelectFunc];
    if($.isFunction(_fun) && !this.flg){
        var _ret = _fun.apply(this,[this.list2]);
        _ret && $('#list2Select').selectpicker('val',_ret);
    }
}

補充說明:
$nextTick方法的使用要特別小心,因為對下拉列表資料的重新賦值都會導致Vue重新渲染Dom,為了避免我們例項元件的自定義方法執行多次,需要增加執行標識。

如此一來我們在使用時:

<html>
...
<my-component data-live-search="true" load-select-func="myFunc"/>
...
<script>
var myFunc = function(options){
    //根據下拉列表資料定製業務邏輯
    //e.g 返回需要選中項
    return option
}
</script>
</html>

如此這是一個結合了實際需求的非典型VUE元件封裝。保持元件例項間的資料一致(這與VUE設計初衷背道而馳)。實際程式碼不做展示,僅簡單說明原理。
如有紕漏,不吝賜教。

轉載請註明出處