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設計初衷背道而馳)。實際程式碼不做展示,僅簡單說明原理。
如有紕漏,不吝賜教。
轉載請註明出處