github地址:https://github.com/yexiaochai/wxdemogit
接上文繼續,咱們前面學習了小程序的生命週期、小程序的標籤、小程序的樣式,後面咱們寫了一個簡單的loading組件,顯然他是個半成品,咱們在作loading組件的時候意識到一個問題:github
小程序的組件事實上是標籤
咱們沒有辦法得到標籤的實例,至少我暫時沒有辦法
因此這些前提讓咱們對標籤的認識有很大的不一樣,完成小程序特有的UI庫,那麼就須要從標籤出發
這裏面關注的點從js中的實例變成了wxml中的屬性
咱們今天嘗試作幾個組件,而後先作未完成的loading,而後作消息類彈出組件,而後作日曆組件,我但願在這個過程當中,咱們造成一套可用的體系,這裏涉及了組件體系,咱們可能須要整理下流程:web
① 首先咱們這裏作的組件實際上是「標籤」,這個時候就要考慮引入時候的怎麼處理了json
② 由於寫業務頁面的同事(寫page的同事),須要在json配置中引入須要使用的標籤:小程序
"usingComponents": { "ui-loading": "/components/ui-loading" }
由於不能動態插入標籤,因此須要一開始就把標籤放入頁面wxml中:app
<ui-loading is-show="{{isLoadingShow}}"></ui-loading>
③ json中的配置暫時只能拷貝,可是咱們能夠提供一個ui-set.wxml來動態引入一些組件,如全局使用的loading彈出類提示框xss
④ 像日曆類組件或者平時用的比較少的彈出層組件便須要本身在頁面中引入了,工做量貌似不大,後續看看狀況,如何優化ide
⑤ 咱們這裏給每一個組件設置一個behaviors,behaviors原則只設置一層(這裏有點繼承的關係),層級多了變比較複雜了,彈出層類是一個、通常類一個(用於日曆類組件)工具
有了以上標準,咱們這裏先來改造咱們的loading組件學習
⑥ 默認全部的組件初期WXSS直接設置爲隱藏
這裏首先改造彈出層都要繼承的behaviors behavior-layer:
1 const util = require('../utils/util.js') 2 module.exports = Behavior({ 3 properties: { 4 //重要屬性,每一個組件必帶,定義組件是否顯示 5 isShow: { 6 type: String 7 } 8 }, 9 //這裏設置彈出層必須帶有一個遮蓋層,因此每一個彈出層都必定具備有個z-index屬性 10 data: { 11 maskzIndex: util.getBiggerzIndex(), 12 uiIndex: util.getBiggerzIndex() 13 }, 14 attached: function() { 15 console.log('layer') 16 }, 17 methods: { 18 } 19 })
其次咱們改造下咱們的mask組件:
1 let LayerView = require('behavior-layer') 2 Component({ 3 behaviors: [LayerView], 4 properties: { 5 //只有mask的z-index屬性須要被調用的彈出層動態設置 6 zIndex: { 7 type: String 8 } 9 }, 10 data: { 11 }, 12 attached: function () { 13 console.log('mask') 14 }, 15 methods: { 16 onTap: function() { 17 this.triggerEvent('customevent', {}, {}) 18 } 19 } 20 })
WXML不作變化,便完成了咱們的代碼,而且結構關係彷佛更加清晰了,可是做爲loading組件實際上是有個問題的,好比點擊遮蓋層要不要關閉整個組件,像相似這種點擊遮蓋層要不要關閉整個組件,其實該是一個公共屬性,因此咱們對咱們的layer、mask繼續進行改造(這裏具體請看github代碼):
1 const util = require('../utils/util.js') 2 module.exports = Behavior({ 3 properties: { 4 //重要屬性,每一個組件必帶,定義組件是否顯示 5 isShow: { 6 type: String 7 } 8 }, 9 //這裏設置彈出層必須帶有一個遮蓋層,因此每一個彈出層都必定具備有個z-index屬性 10 data: { 11 maskzIndex: util.getBiggerzIndex(), 12 uiIndex: util.getBiggerzIndex(), 13 //默認點擊遮蓋層不關閉組件 14 clickToHide: false 15 }, 16 attached: function() { 17 console.log('layer') 18 }, 19 methods: { 20 } 21 })
1 methods: { 2 onMaskEvent: function (e) { 3 console.log(e); 4 //若是設置了點擊遮蓋層關閉組件則關閉 5 if (this.data.clickToHide) 6 this.setData({ 7 isShow: 'none' 8 }); 9 } 10 }
這個時候,點擊要不要關閉,基本就在組件裏面設置一個屬性便可,可是咱們這個做爲了內部屬性,沒有釋放出去,這個時候咱們也許發現了另一個比較幽默的場景了:
咱們由於無法獲取一個標籤的實例,因此咱們須要在頁面裏面動態調用:
1 onShow: function() { 2 let scope= this; 3 this.setData({ 4 isLoadingShow: '' 5 }); 6 //3秒後關閉loading 7 setTimeout(function () { 8 scope.setData({ 9 isLoadingShow: 'none' 10 }); 11 }, 3000); 12 },
能夠看到,標籤接入到頁面後,控制標籤事實上是動態操做他的屬性,也就是說操做頁面的狀態數據,頁面的UI變化所有是數據觸發,這樣的邏輯會讓界面變得更加清晰,可是做爲全局類的loading這種參數,我並不想放到各個頁面中,由於這樣會致使不少重複代碼,因而我在utils目錄中新建了一個ui-util的工具類,做爲一些全局類的ui公共庫:
1 //由於小程序頁面中每一個頁面應該是獨立的做用域 2 class UIUtil { 3 constructor(opts) { 4 //用於存儲各類默認ui屬性 5 this.isLoadingShow = 'none'; 6 } 7 //產出頁面loading須要的參數 8 getPageData() { 9 return { 10 isLoadingShow: this.isLoadingShow 11 } 12 } 13 //須要傳入page實例 14 showLoading(page) { 15 this.isLoadingShow = ''; 16 page.setData({ 17 isLoadingShow: this.isLoadingShow 18 }); 19 } 20 //關閉loading 21 hideLoading(page) { 22 this.isLoadingShow = 'none'; 23 page.setData({ 24 isLoadingShow: this.isLoadingShow 25 }); 26 } 27 } 28 29 //直接返回一個UI工具了類的實例 30 module.exports = new UIUtil
index.js使用上產生一點變化:
1 //獲取公共ui操做類實例 2 const uiUtil = require('../../utils/ui-util.js'); 3 //獲取應用實例 4 const app = getApp() 5 Page({ 6 data: uiUtil.getPageData(), 7 onShow: function() { 8 let scope= this; 9 uiUtil.showLoading(this); 10 //3秒後關閉loading 11 setTimeout(function () { 12 uiUtil.hideLoading(scope); 13 }, 3000); 14 }, 15 onLoad: function () { 16 } 17 })
這樣,咱們將頁面裏面要用於操做組件的數據所有放到了一個util類中,這樣代碼會變得清晰一些,組件管理也放到了一個地方,只是命名規範必定要安規則來,彷佛到這裏,咱們的loading組件改造結束了,這裏卻有一個問題,咱們在ui-util類中存儲的事實上是頁面級的數據,其中包含是組件的狀態,可是真實狀況咱們點擊遮蓋層關閉組件,根本不會知會page層的數據,這個時候咱們loading的顯示狀態搞很差是顯示,而真實的組件已經關閉了,如何保證狀態統一咱們後面點再說,我暫時沒有想到好的辦法。
咱們如今先繼續做toast組件,toast組件同樣包含一個遮蓋層,可是點擊的時候能夠關閉遮蓋層,顯示3秒後關閉,顯示多久關閉的屬性應該是能夠配置的(做爲屬性傳遞),因此咱們新增組件:
1 const util = require('../utils/util.js'); 2 let LayerView = require('behavior-layer'); 3 4 Component({ 5 behaviors: [ 6 LayerView 7 ], 8 properties: { 9 message: { 10 type: String 11 } 12 }, 13 data: { 14 }, 15 attached: function () { 16 console.log(this) 17 }, 18 methods: { 19 onMaskEvent: function (e) { 20 console.log(e); 21 //若是設置了點擊遮蓋層關閉組件則關閉 22 if (this.data.clickToHide) 23 this.setData({ 24 isShow: 'none' 25 }); 26 } 27 } 28 })
總體代碼請各位在git上面去看,這裏也引發了一些問題:
① 個人組件如何居中?
② 通常來講toast消失的時候是能夠定製化一個事件回調的,咱們這裏怎麼實現?
這裏咱們先拋開居中問題,咱們先來解決第二個問題,由於小程序中沒有addEventListener這個方法,因此可以改變組件特性的方式只剩下數據操做,回顧咱們這裏能夠引發組件隱藏的點只有:
① toast中的點擊彈出層時改變顯示屬性
1 onMaskEvent: function (e) { 2 console.log(e); 3 //若是設置了點擊遮蓋層關閉組件則關閉 4 if (this.data.clickToHide) 5 this.setData({ 6 isShow: 'none' 7 }); 8 }
② 而後就是頁面中動態改變數據屬性了:
1 onShow: function() { 2 let scope= this; 3 uiUtil.showToast(this, '我是美麗可愛的toast'); 4 //3秒後關閉loading 5 setTimeout(function () { 6 uiUtil.hideToast(scope); 7 }, 3000); 8 },
這裏,咱們不得不處理以前的數據同步問題了,咱們應該給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組件:
1 //behavior-layer 2 const util = require('../utils/util.js') 3 module.exports = Behavior({ 4 properties: { 5 //重要屬性,每一個組件必帶,定義組件是否顯示 6 isShow: { 7 type: String 8 } 9 }, 10 //這裏設置彈出層必須帶有一個遮蓋層,因此每一個彈出層都必定具備有個z-index屬性 11 data: { 12 maskzIndex: util.getBiggerzIndex(), 13 uiIndex: util.getBiggerzIndex(), 14 //默認點擊遮蓋層不關閉組件 15 clickToHide: true 16 }, 17 attached: function() { 18 console.log('layer') 19 }, 20 methods: { 21 onMaskEvent: function (e) { 22 this.triggerEvent('maskevent', e, {}) 23 } 24 } 25 })
1 .cm-overlay { 2 background: rgba(0, 0, 0, 0.5); 3 position: fixed; 4 top: 0; 5 right: 0; 6 bottom: 0; 7 left: 0; 8 } 9 10 .cm-modal { 11 background-color: #fff; 12 overflow: hidden; 13 width: 100%; 14 border-radius: 8rpx; 15 } 16 17 .cm-modal--toast { 18 width: auto; 19 margin-top: -38rpx; 20 background: rgba(0, 0, 0, 0.7); 21 color: #fff; 22 padding: 20rpx 30rpx; 23 text-align: center; 24 font-size: 24rpx; 25 white-space: nowrap; 26 position: fixed; 27 top: 50%; 28 left: 50%; 29 30 } 31 .cm-modal--toast .icon-right { 32 display: inline-block; 33 margin: 10rpx 0 24rpx 10rpx; 34 } 35 .cm-modal--toast .icon-right::before { 36 content: ""; 37 display: block; 38 width: 36rpx; 39 height: 16rpx; 40 border-bottom: 4rpx solid #fff; 41 border-left: 4rpx solid #fff; 42 -webkit-transform: rotate(-45deg); 43 transform: rotate(-45deg); 44 -webkit-box-sizing: border-box; 45 box-sizing: border-box; 46 }
1 <section class="cm-modal cm-modal--toast" style="z-index: {{uiIndex}}; display: {{isShow}}; "> 2 {{message}} 3 </section> 4 <view class="cm-overlay" bindtap="onMaskEvent" style="z-index: {{maskzIndex}}; display: {{isShow}}" > 5 </view>
1 const util = require('../utils/util.js'); 2 let LayerView = require('behavior-layer'); 3 Component({ 4 behaviors: [ 5 LayerView 6 ], 7 properties: { 8 message: { 9 type: String 10 } 11 }, 12 data: { 13 }, 14 attached: function () { 15 console.log(this) 16 }, 17 methods: { 18 } 19 })
頁面層的使用沒必要變化就已經面目一新了,這個時候咱們開始作ui-util與page關係的改造,看看能不能讓咱們的代碼變得簡單,我這裏的思路是設計一個公共的abstract-view出來,作全部頁面的基類:
1 class Page { 2 constructor(opts) { 3 //用於基礎page存儲各類默認ui屬性 4 this.isLoadingShow = 'none'; 5 this.isToastShow = 'none'; 6 this.toastMessage = 'toast提示'; 7 8 //通用方法列表配置,暫時約定用於點擊 9 this.methodSet = [ 10 'onToastHide', 'showToast', 'hideToast', 'showLoading', 'hideLoading' 11 ]; 12 13 //當前page對象 14 this.page = null; 15 } 16 initPage(pageData) { 17 //debugger; 18 19 let _pageData = {}; 20 21 //爲頁面動態添加操做組件的方法 22 Object.assign(_pageData, this.getPageFuncs(), pageData); 23 24 //生成真實的頁面數據 25 _pageData.data = {}; 26 Object.assign(_pageData.data, this.getPageData(), pageData.data || {}); 27 28 console.log(_pageData); 29 return _pageData; 30 } 31 //當關閉toast時觸發的事件 32 onToastHide(e) { 33 this.hideToast(); 34 } 35 //設置頁面可能使用的方法 36 getPageFuncs() { 37 let funcs = {}; 38 for (let i = 0, len = this.methodSet.length; i < len; i++ ) { 39 funcs[this.methodSet[i]] = this[this.methodSet[i]]; 40 } 41 return funcs; 42 } 43 //產出頁面組件須要的參數 44 getPageData() { 45 return { 46 isLoadingShow: this.isLoadingShow, 47 isToastShow: this.isToastShow, 48 toastMessage: this.toastMessage 49 } 50 } 51 showToast(message) { 52 this.setData({ 53 isToastShow: '', 54 toastMessage: message 55 }); 56 } 57 hideToast() { 58 this.setData({ 59 isToastShow: 'none' 60 }); 61 } 62 //須要傳入page實例 63 showLoading() { 64 this.setData({ 65 isLoadingShow: '' 66 }); 67 } 68 //關閉loading 69 hideLoading() { 70 this.setData({ 71 isLoadingShow: 'none' 72 }); 73 } 74 } 75 //直接返回一個UI工具了類的實例 76 module.exports = new Page
這裏還提供了一個公共模板用於被頁面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"/>
1 //獲取公共ui操做類實例 2 const _page = require('../../utils/abstract-page.js'); 3 //獲取應用實例 4 const app = getApp() 5 6 Page(_page.initPage({ 7 data: { 8 ttt: 'ttt' 9 10 }, 11 // methods: uiUtil.getPageMethods(), 12 methods: { 13 }, 14 onShow: function () { 15 let scope = this; 16 this.showToast('我是美麗可愛的toast'); 17 // 3秒後關閉loading 18 // setTimeout(function () { 19 // scope.hideToast(); 20 // }, 3000); 21 }, 22 onLoad: function () { 23 // this.setPageMethods(); 24 } 25 }))
這樣咱們至關於變相給page賦能了,詳情請各位看github上的代碼:https://github.com/yexiaochai/wxdemo,這個時候,咱們要爲toast組件添加關閉時候的事件回調,就變得相對簡單了,事實上咱們能夠看到這個行爲已經跟組件自己沒有太多關係了:
1 showToast(message, callback) { 2 this.toastHideCallback = null; 3 if (callback) this.toastHideCallback = callback; 4 let scope = this; 5 this.setData({ 6 isToastShow: '', 7 toastMessage: message 8 }); 9 10 // 3秒後關閉loading 11 setTimeout(function () { 12 scope.hideToast(); 13 }, 3000); 14 } 15 hideToast() { 16 this.setData({ 17 isToastShow: 'none' 18 }); 19 if (this.toastHideCallback) this.toastHideCallback.call(this); 20 }
this.showToast('我是美麗可愛的toast', function () { console.log('執行回調')} );
固然這裏能夠作得更加人性化,好比顯示時間是根據message長度動態設置的,咱們這裏先這樣。
本篇篇幅已經比較長了,咱們最後完成一個alert組件便結束今天的學習,明天主要實現日曆等組件,alert組件通常是一個帶肯定框的提示彈出層,有可能有兩個按鈕,那個狀況要稍微複雜點,咱們這裏依舊爲其新增組件結構wxml以及wxss:
1 //獲取公共ui操做類實例 2 const _page = require('../../utils/abstract-page.js'); 3 //獲取應用實例 4 const app = getApp() 5 6 Page(_page.initPage({ 7 data: { 8 }, 9 // methods: uiUtil.getPageMethods(), 10 methods: { 11 }, 12 onShow: function () { 13 global.sss = this; 14 let scope = this; 15 this.showMessage({ 16 message: '我是一個肯定框', 17 ok: { 18 name: '肯定', 19 callback: function () { 20 scope.hideMessage(); 21 scope.showMessage('我選擇了肯定'); 22 } 23 }, 24 cancel: { 25 name: '取消', 26 callback: function () { 27 scope.hideMessage(); 28 scope.showToast('我選擇了取消'); 29 } 30 } 31 }); 32 33 }, 34 onLoad: function () { 35 // this.setPageMethods(); 36 } 37 }))
github地址:https://github.com/yexiaochai/wxdemo
今天咱們彷佛找到了一個適合小程序的組件編寫方式,明天咱們繼續完成一些組件,組件完成後咱們便開始寫實際業務代碼了