小程序開發

上海小程序開發 > 開發教程 > 微信小程序開發(4):打造自己的UI庫

微信小程序開發(4):打造自己的UI庫

編輯時間:03-04 17:46 瀏覽次數:58

前言


github地址:https://github.com/yexiaochai/wxdemo


接上文繼續,我們前面學習了小程序的生命周期、小程序的標簽、小程序的樣式,后面我們寫了一個簡單的loading組件,顯然他是個半成品,我們在做loading組件的時候意識到一個問題:


小程序的組件事實上是標簽

我們沒有辦法獲得標簽的實例,至少我暫時沒有辦法

所以這些前提讓我們對標簽的認識有很大的不同,完成小程序特有的UI庫,那么就需要從標簽出發

這里面關注的點從js中的實例變成了wxml中的屬性


我們今天嘗試做幾個組件,然后先做未完成的loading,然后做消息類彈出組件,然后做日歷組件,我希望在這個過程中,我們形成一套可用的體系,這里涉及了組件體系,我們可能需要整理下流程:


① 首先我們這里做的組件其實是“標簽”,這個時候就要考慮引入時候的怎么處理了


② 因為寫業務頁面的同事(寫page的同事),需要在json配置中引入需要使用的標簽:


"usingComponents": {

  "ui-loading": "/components/ui-loading"

}


因為不能動態插入標簽,所以需要一開始就把標簽放入頁面wxml中:


<ui-loading is-show="{{isLoadingShow}}"></ui-loading>


③ json中的配置暫時只能拷貝,但是我們可以提供一個ui-set.wxml來動態引入一些組件,如全局使用的loading彈出類提示框


④ 像日歷類組件或者平時用的比較少的彈出層組件便需要自己在頁面中引入了,工作量貌似不大,后續看看情況,如何優化


⑤ 我們這里給每個組件設置一個behaviors,behaviors原則只設置一層(這里有點繼承的關系),層級多了變比較復雜了,彈出層類是一個、一般類一個(用于日歷類組件)


有了以上標準,我們這里先來改造我們的loading組件


⑥ 默認所有的組件初期WXSS直接設置為隱藏


改造loading


這里首先改造彈出層都要繼承的behaviors behavior-layer:


const util = require('../utils/util.js')

module.exports = Behavior({

  properties: {

    //重要屬性,每個組件必帶,定義組件是否顯示

    isShow: {

      type: String

    }

  },

  //這里設置彈出層必須帶有一個遮蓋層,所以每個彈出層都一定具有有個z-index屬性

  data: {

    maskzIndex: util.getBiggerzIndex(),

    uiIndex: util.getBiggerzIndex()

  },

  attached: function() {

    console.log('layer')

  },

  methods: {

  }

})


其次我們改造下我們的mask組件:


let LayerView = require('behavior-layer')

Component({

  behaviors: [LayerView],

  properties: {

    //只有mask的z-index屬性需要被調用的彈出層動態設置

    zIndex: {

      type: String

    }

  },

  data: {

  },

  attached: function () {

    console.log('mask')

  },

  methods: {

    onTap: function() {

      this.triggerEvent('customevent', {}, {})

    }

  }

})


WXML不做變化,便完成了我們的代碼,并且結構關系似乎更加清晰了,但是作為loading組件其實是有個問題的,比如點擊遮蓋層要不要關閉整個組件,像類似這種點擊遮蓋層要不要關閉整個組件,其實該是一個公共屬性,所以我們對我們的layer、mask繼續進行改造(這里具體請看github代碼):


const util = require('../utils/util.js')

module.exports = Behavior({

  properties: {

    //重要屬性,每個組件必帶,定義組件是否顯示

    isShow: {

      type: String

    }

  },

  //這里設置彈出層必須帶有一個遮蓋層,所以每個彈出層都一定具有有個z-index屬性

  data: {

    maskzIndex: util.getBiggerzIndex(),

    uiIndex: util.getBiggerzIndex(),

    //默認點擊遮蓋層不關閉組件

    clickToHide: false

  },

  attached: function() {

    console.log('layer')

  },

  methods: {

  }

})


methods: {

  onMaskEvent: function (e) {

    console.log(e);

    //如果設置了點擊遮蓋層關閉組件則關閉

    if (this.data.clickToHide)

      this.setData({

        isShow: 'none'

      });

  }

}


這個時候,點擊要不要關閉,基本就在組件里面設置一個屬性即可,但是我們這個作為了內部屬性,沒有釋放出去,這個時候我們也許發現了另外一個比較幽默的場景了:


我們因為沒法獲取一個標簽的實例,所以我們需要在頁面里面動態調用:


onShow: function() {

  let scope= this;

  this.setData({

    isLoadingShow: ''

  });

  //3秒后關閉loading

  setTimeout(function () {

    scope.setData({

      isLoadingShow: 'none'

    });

  }, 3000);

},


可以看到,標簽接入到頁面后,控制標簽事實上是動態操作他的屬性,也就是說操作頁面的狀態數據,頁面的UI變化全部是數據觸發,這樣的邏輯會讓界面變得更加清晰,但是作為全局類的loading這種參數,我并不想放到各個頁面中,因為這樣會導致很多重復代碼,于是我在utils目錄中新建了一個ui-util的工具類,作為一些全局類的ui公共庫:


//因為小程序頁面中每個頁面應該是獨立的作用域

class UIUtil {

  constructor(opts) {

    //用于存儲各種默認ui屬性

    this.isLoadingShow = 'none';

  }

  //產出頁面loading需要的參數

  getPageData() {

    return {

      isLoadingShow: this.isLoadingShow

    }

  }

  //需要傳入page實例

  showLoading(page) {

    this.isLoadingShow = '';

    page.setData({

      isLoadingShow: this.isLoadingShow

    });

  }

  //關閉loading

  hideLoading(page) {

    this.isLoadingShow = 'none';

    page.setData({

      isLoadingShow: this.isLoadingShow

    });

  }

}

 

//直接返回一個UI工具了類的實例

module.exports = new UIUtil


index.js使用上產生一點變化:


//獲取公共ui操作類實例

const uiUtil = require('../../utils/ui-util.js');

//獲取應用實例

const app = getApp()

Page({

  data: uiUtil.getPageData(),

  onShow: function() {

    let scope= this;

    uiUtil.showLoading(this);

    //3秒后關閉loading

    setTimeout(function () {

      uiUtil.hideLoading(scope);

    }, 3000);

  },

  onLoad: function () {

  }

})


這樣,我們將頁面里面要用于操作組件的數據全部放到了一個util類中,這樣代碼會變得清晰一些,組件管理也放到了一個地方,只是命名規范一定要安規則來,似乎到這里,我們的loading組件改造結束了,這里卻有一個問題,我們在ui-util類中存儲的事實上是頁面級的數據,其中包含是組件的狀態,但是真實情況我們點擊遮蓋層關閉組件,根本不會知會page層的數據,這個時候我們loading的顯示狀態搞不好是顯示,而真實的組件已經關閉了,如何保證狀態統一我們后面點再說,我暫時沒有想到好的辦法。


toast組件


我們現在先繼續作toast組件,toast組件一樣包含一個遮蓋層,但是點擊的時候可以關閉遮蓋層,顯示3秒后關閉,顯示多久關閉的屬性應該是可以配置的(作為屬性傳遞),所以我們新增組件:


微信小程序開發(4):打造自己的UI庫


const util = require('../utils/util.js');

let LayerView = require('behavior-layer');

 

Component({

  behaviors: [

    LayerView

  ],

  properties: {

    message: {

      type: String

    }

  },

  data: {

  },

  attached: function () {

    console.log(this)

  },

  methods: {

    onMaskEvent: function (e) {

      console.log(e);

      //如果設置了點擊遮蓋層關閉組件則關閉

      if (this.data.clickToHide)

        this.setData({

          isShow: 'none'

        });

    }

  }

})


微信小程序開發(4):打造自己的UI庫


整體代碼請各位在git上面去看,這里也引起了一些問題:


① 我的組件如何居中?


② 一般來說toast消失的時候是可以定制化一個事件回調的,我們這里怎么實現?


這里我們先拋開居中問題,我們先來解決第二個問題,因為小程序中沒有addEventListener這個方法,所以能夠改變組件特性的方式只剩下數據操作,回顧我們這里可以引起組件隱藏的點只有:


① toast中的點擊彈出層時改變顯示屬性


onMaskEvent: function (e) {

  console.log(e);

  //如果設置了點擊遮蓋層關閉組件則關閉

  if (this.data.clickToHide)

    this.setData({

      isShow: 'none'

    });

}


② 然后就是頁面中動態改變數據屬性了:


onShow: function() {

  let scope= this;

  uiUtil.showToast(this, '我是美麗可愛的toast');

  //3秒后關閉loading

  setTimeout(function () {

    uiUtil.hideToast(scope);

  }, 3000);

},


這里,我們不得不處理之前的數據同步問題了,我們應該給toast提供一個事件屬性可定義的點,點擊遮蓋層的真正處理邏輯需要放到page層,其實認真思考下,標簽就應該很純粹,不應該與業務相關,只需要提供鉤子,與業務相關的是page中的業務,這個時候大家可以看到我們代碼之間的關聯是多么的復雜了:


① 頁面index.js依賴于index.wxml中組件的標簽,并且依賴于uiUtil這個工具類


② 單單一個toast組件(標簽)便依賴了mask標簽,一個工具欄,還有基礎的layer behavior


③ 因為不能獲取實例,所以組件直接通信只能通過標簽的bindevent的做法,讓情況變得更加詭異


從這里看起來,調用方式也著實太復雜了,而這還僅僅是一個簡單的組件,這個是不是我們寫法有問題呢?答案是!我的思路還是以之前做js的組件的思路,但是小程序暫時不支持動態插入標簽,所以我們不應該有過多的繼承關系,其中的mask是沒有必要的;另一方面,每個頁面要動態引入ui-utils這個莫名其妙的組件庫,似乎也很別扭,所以我們這里準備進行改造,降低沒有必要的復雜度


組件改造


經過思考,我們這里準備做以下優化(PS:我小程序也是上星期開始學習的,需要逐步摸索):


① 保留mask組件,但是去除toast、loading類組件與其關聯,將WXML以及樣式直接內聯,使用空間復雜度降低代碼復雜度


② 取消ui-uitil攻擊類,轉而實現一個page基類


我們這里先重新實現toast組件:


//behavior-layer

const util = require('../utils/util.js')

module.exports = Behavior({

  properties: {

    //重要屬性,每個組件必帶,定義組件是否顯示

    isShow: {

      type: String

    }

  },

  //這里設置彈出層必須帶有一個遮蓋層,所以每個彈出層都一定具有有個z-index屬性

  data: {

    maskzIndex: util.getBiggerzIndex(),

    uiIndex: util.getBiggerzIndex(),

    //默認點擊遮蓋層不關閉組件

    clickToHide: true

  },

  attached: function() {

    console.log('layer')

  },

  methods: {

    onMaskEvent: function (e) {

      this.triggerEvent('maskevent', e, {})

    }

  }

})



.cm-overlay {

    background: rgba(0, 0, 0, 0.5);

    position: fixed;

    top: 0;

    right: 0;

    bottom: 0;

    left: 0;

}

 

.cm-modal {

  background-color: #fff;

  overflow: hidden;

  width: 100%;

  border-radius: 8rpx;

}

 

.cm-modal--toast {

  width: auto;

  margin-top: -38rpx;

  background: rgba(0, 0, 0, 0.7);

  color: #fff;

  padding: 20rpx 30rpx;

  text-align: center;

  font-size: 24rpx;

  white-space: nowrap;

  position: fixed;

  top: 50%;

  left: 50%;

 

}

.cm-modal--toast .icon-right {

  display: inline-block;

  margin: 10rpx 0 24rpx 10rpx;

}

.cm-modal--toast .icon-right::before {

  content: "";

  display: block;

  width: 36rpx;

  height: 16rpx;

  border-bottom: 4rpx solid #fff;

  border-left: 4rpx solid #fff;

  -webkit-transform: rotate(-45deg);

          transform: rotate(-45deg);

  -webkit-box-sizing: border-box;

          box-sizing: border-box;

}



<section class="cm-modal cm-modal--toast" style="z-index: {{uiIndex}}; display: {{isShow}}; ">

  {{message}}

</section>

<view class="cm-overlay" bindtap="onMaskEvent" style="z-index: {{maskzIndex}}; display: {{isShow}}" >

</view>


const util = require('../utils/util.js');

let LayerView = require('behavior-layer');

Component({

  behaviors: [

    LayerView

  ],

  properties: {

    message: {

      type: String

    }

  },

  data: {

  },

  attached: function () {

    console.log(this)

  },

  methods: {

  }

})


頁面層的使用不必變化就已經煥然一新了,這個時候我們開始做ui-util與page關系的改造,看看能不能讓我們的代碼變得簡單,我這里的思路是設計一個公共的abstract-view出來,做所有頁面的基類:



class Page {

    constructor(opts) {

        //用于基礎page存儲各種默認ui屬性

        this.isLoadingShow = 'none';

        this.isToastShow = 'none';

        this.toastMessage = 'toast提示';

 

        //通用方法列表配置,暫時約定用于點擊

        this.methodSet = [

            'onToastHide', 'showToast', 'hideToast', 'showLoading', 'hideLoading'

        ];

 

        //當前page對象

        this.page = null;

    }

    initPage(pageData) {

        //debugger;

 

        let _pageData = {};

 

        //為頁面動態添加操作組件的方法

        Object.assign(_pageData, this.getPageFuncs(), pageData);

 

        //生成真實的頁面數據

        _pageData.data = {};

        Object.assign(_pageData.data, this.getPageData(), pageData.data || {});

 

        console.log(_pageData);

        return _pageData;

    }

    //當關閉toast時觸發的事件

    onToastHide(e) {

        this.hideToast();

    }

    //設置頁面可能使用的方法

    getPageFuncs() {

        let funcs = {};

        for (let i = 0, len = this.methodSet.length; i < len; i++ ) {

            funcs[this.methodSet[i]] = this[this.methodSet[i]];

        }

        return funcs;

    }

    //產出頁面組件需要的參數

    getPageData() {

        return {

            isLoadingShow: this.isLoadingShow,

            isToastShow: this.isToastShow,

            toastMessage: this.toastMessage

        }

    }

    showToast(message) {

        this.setData({

            isToastShow: '',

            toastMessage: message

        });

    }

    hideToast() {

        this.setData({

            isToastShow: 'none'

        });

    }

    //需要傳入page實例

    showLoading() {

        this.setData({

            isLoadingShow: ''

        });

    }

    //關閉loading

    hideLoading() {

        this.setData({

            isLoadingShow: 'none'

        });

    }

}

//直接返回一個UI工具了類的實例

module.exports = new Page

 

abstract-view


這里還提供了一個公共模板用于被頁面include,abstract-view.wxml:


<ui-toast bindonToastHide="onToastHide" is-show="{{isToastShow}}" message="{{toastMessage}}"></ui-toast>


頁面調用時候的代碼發生了很大的變化:


<import src="./mod.searchbox.wxml" />

<view>

  <template is="searchbox" />

</view>

<include src="../../utils/abstract-page.wxml"/>


//獲取公共ui操作類實例

const _page = require('../../utils/abstract-page.js');

//獲取應用實例

const app = getApp()

 

Page(_page.initPage({

  data: {

    ttt: 'ttt'

 

  },

  // methods: uiUtil.getPageMethods(),

  methods: {

  },

  onShow: function () {

     let scope = this;

     this.showToast('我是美麗可愛的toast');

     // 3秒后關閉loading

    //  setTimeout(function () {

    //    scope.hideToast();

    //  }, 3000);

  },

  onLoad: function () {

    // this.setPageMethods();

  }

}))


這樣我們相當于變相給page賦能了,詳情請各位看github上的代碼:https://github.com/yexiaochai/wxdemo,這個時候,我們要為toast組件添加關閉時候的事件回調,就變得相對簡單了,事實上我們可以看到這個行為已經跟組件本身沒有太多關系了:


showToast(message, callback) {

  this.toastHideCallback = null;

  if (callback) this.toastHideCallback = callback;

  let scope = this;

  this.setData({

    isToastShow: '',

    toastMessage: message

  });

 

  // 3秒后關閉loading

  setTimeout(function () {

    scope.hideToast();

  }, 3000);

}

hideToast() {

  this.setData({

    isToastShow: 'none'

  });

  if (this.toastHideCallback) this.toastHideCallback.call(this);

}


this.showToast('我是美麗可愛的toast', function () { console.log('執行回調')} );


當然這里可以做得更加人性化,比如顯示時間是根據message長度動態設置的,我們這里先這樣。


alert類組件


本篇篇幅已經比較長了,我們最后完成一個alert組件便結束今天的學習,明天主要實現日歷等組件,alert組件一般是一個帶確定框的提示彈出層,有可能有兩個按鈕,那個情況要稍微復雜點,我們這里依舊為其新增組件結構wxml以及wxss:


//獲取公共ui操作類實例

const _page = require('../../utils/abstract-page.js');

//獲取應用實例

const app = getApp()

 

Page(_page.initPage({

  data: {

  },

  // methods: uiUtil.getPageMethods(),

  methods: {

  },

  onShow: function () {

    global.sss = this;

    let scope = this;

    this.showMessage({

      message: '我是一個確定框',

      ok: {

        name: '確定',

        callback: function () {

          scope.hideMessage();

          scope.showMessage('我選擇了確定');

        }

      },

      cancel: {

        name: '取消',

        callback: function () {

          scope.hideMessage();

          scope.showToast('我選擇了取消');

        }

      }

    });

 

  },

  onLoad: function () {

    // this.setPageMethods();

  }

}))


結語


github地址:https://github.com/yexiaochai/wxdemo

免責聲明:我司網站轉載此文,不代表本網的觀點和立場。不以盈利為目的,如有侵犯公司或個人權益,我司會第一時間刪除文章,歡迎咨詢免費獲取思維導圖!
感受專業服務,從來電咨詢開始
15821967367158-2196-7367
cache
Processed in 0.004831 Second.
浙江20选5几点开奖