程式碼實現
單從元件功能而言,wepy已經滿足了小程式絕大部分的元件開發需求,堪稱完美。 但如果權衡整個專案:
- 開發前需要檢視開發文件,按照與小程式截然不同的寫法編寫小程式。對於不熟悉MVVM開發來說有一定的學習成本,而且對於不利於開發者熟悉小程式(就像一個前端開發的入門者就直接使用React.js開發,這樣很難熟悉諸如原生JS操作DOM的“相對底層”知識)。
- 開發時也需要實時編譯之後才能預覽和除錯,有一定的時間開銷。
- 開發編譯後的程式碼未能正常執行,到底是編寫問題還是編譯問題還是框架問題?除錯起來具有一定的複雜性。
所以wepy更適合大型小程式(當你有足夠理由使用它時),就像Vuex官方文件中提到的那樣:如果您的應用夠簡單,您最好不要使用 Vuex。一個簡單的 global event bus 就足夠您所需了。
如果對於中小型的小程式,怎麼較好的實現元件化?
wxss毫無異議地採用@import引用。
而對於模板,由於我們使用include方式引用,所以只能和wepy一樣,通過命名的方式來隔離作用域。最簡單的方式莫過於給其增加元件名成做字首。譬如:
1 2 3 4 5 6 7 8 |
// src/components/tab.wxml <view class="tab"> <view class="tab_item tab_message{{tabActive == 0 ? ' active' : ''}}" bindtap="tabChange(0)"> <image class="icon" src="../images/message{{tabActive == 0 ? '_active' : ''}}.png"></image> <text class="title">微信</text> </view> ... </view> |
而對於js可以簡單封裝成物件,然後直接與頁面物件合併即可:
1 2 3 4 5 6 7 8 9 10 11 |
// src/components/tab.js export default class { data: { tabActive: { twoWay: true } }, change (idx, evt) { this.active = +idx; } } |
很多人都會想到這樣呼叫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// src/pages/index.wxml <include src="../components/tab.wxml"/> ... // src/pages/index.js import tab from '../components/tab.js' let page = { ... } Object.assign(page, tab) Page(page) |
直接用Object.assign
來實現最大的壞處就是父頁面無法監聽元件事件,比如在例子中tab頁籤被點選的時候,tab元件內部需要做樣式的變化,而對於父頁面也需要監聽這個事件來展現對應的內容。
另一種情況,比如元件初始化也需要用到父頁面的data屬性的時候,那麼需要父頁面在初始化完成之後再呼叫元件本身的初始化函式。 在onLoad中依次呼叫?這樣需要對每個元件進行判斷和呼叫,太多垃圾程式碼也容易出錯,顯然程式碼不夠簡介和健壯。
藉助MVVM框架的開發經驗,我們可以採用Vue.js中的mixin思路來解決這個問題。
將元件和頁面物件進行混合。對於元件中的data屬性,我們依然採取Object.assign
的方式拷貝,由於元件物件中我們只要區分資料型別和事件函式,那麼可以去掉data這一層。而對於事件我們需要既觸發元件事件,也觸發頁面事件,那麼需要寫段程式碼來實現。最後元件的定義和呼叫可以修改為:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// src/components/tab.js export default class { tabActive: { twoWay: true }, change (idx, evt) { this.active = +idx; } } // src/pages/index.js import tab from '../components/tab.js' import {combine} from '../util.js' let page = { tabClick(event) { ... } ... } combine(page, tab) Page(page) |
而對於combine函式只需要判斷資料型別然後執行對應的操作即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
// util.js /** * 元件物件與頁面物件融合 * @param {Object} target 頁面物件 * @param {Object} source 元件物件,支援不定引數 * return {Object} 返回一個融合後的頁面物件 */ let combine = (target, ...source) => { source.forEach(function(arg) { if('object' === typeof arg) { for(let p in arg) { if('object' === typeof arg[p]) { // 對於物件,直接採用 Object.assign target[p] = target[p] || {} Object.assign(target[p], arg[p]) } else if('function' === typeof arg[p]) { // 函式進行融合,先呼叫元件事件,然後呼叫父頁面事件 let fun = target[p] ? target[p] : function(){} delete target[p] target[p] = function() { arg[p].apply(this, arguments) fun.apply(this, arguments) } } else { // 基礎資料型別,直接覆蓋 target[p] = target[p] || arg[p] } } } }) } export default { combine } |
這種方案似乎簡單明瞭地實現了元件化,但是有一個問題,那就是元件地複用。 如果一個頁面中同時使用多個tab元件就會出現一些麻煩,因為相同元件沒有通過命名進行隔離,所以會有影響。 解決方法只能通過修改元件本身或其它方式來支援了,不過好在大部分頁面中不會出現這樣的需求。
總結
對於大型專案,學有餘力的開發者,可以採用wepy這個非常優秀的小程式框架(既有構建工具,也有程式碼)。 對於中小型專案和初學者,採取混合的方式能滿足元件化開發需求。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式