前言
前面我們研究了下微信小程式的執行流程,因為拿不到原始碼,只能算我們的猜想,我們需要更加了解小程式還需要做具體的專案,於是我們將原來那套還算複雜的業務拿出來:
【元件化開發】前端進階篇之如何編寫可維護可升級的程式碼(有些晦澀有些亂,但是對於整體瞭解小程式結構有幫助)
我們用小程式實現這裡的程式碼,看看是個什麼樣的體驗,另外我這裡想保證程式碼最大程度重用,為後續一端程式碼四端執行做前驅探索。
頁面複雜度還是比較高的,包括了:
① 彈出層
② 頁面跳轉
③ 快取
④ 資料請求
⑤ 列表頁、滾動分頁
⑥ ……
我相信完成了這個例子,我們對小程式業務程式碼怎麼寫會有比較好的瞭解,於是讓我們開始今天的程式碼吧。
小程式的佈局
為什麼不使用HTML&CSS
微信小程式這種平臺型的超越Hybrid系統誕生還是有一些客觀條件的,其中一個就是移動端的應用相對來說簡單的多,想想PC負責的佈局,如果要使用小程式實現,那麼複雜度會提高很多。
小程式程式碼編寫邏輯層依舊使用JS完成,但是結構層以及樣式層推出了:
① WXML,Weixin Markup Language,是微信設計的一套標籤語言,與HTML類似,做過React&Vue的同學會非常熟悉
② WXSS,WeiXin Style Sheets,是一套樣式語言,用於定義樣式,與CSS類似,一般認為是CSS的子集
因為小程式中UI元件都是Native實現,所以小程式直接手起刀落壓根放棄讓我們使用HTML容器,這樣做我覺得有個好處是:
為了更好的限制,我之前也在做Hybrid乃至前端框架,一般來說我會限制到View級別的實習,要求必須按照我的規則做,但是因為入口為index.html檔案,我甚至將全域性控制器App的例項化放到了main.js裡面,只提供了建議的做法,事實上HTML還是太過靈活,有些同事逐漸根本不按照我們的規則玩,他覺得他的做法更好,但是這樣一來便會破壞了專案的總體性,後續的工程性的優化或者監控可能就不能幫助他了,從某個角度來說,我是認可小程式的做法的。
我們之前在這裡研究過自定義標籤的做法:從DOM操作看Vue&React的前端元件化,順帶補齊React的demo
1 2 3 4 5 6 |
<article class="cm-page page-list" id="main"> <div class="js_sort_wrapper sort-bar-wrapper"> <mySortBar :entity="sortEntity"></mySortBar> </div> <myList :entity="listEntity" :sort="sort"></myList> </article> |
從這個文章以及小程式的實現可以看出基本的概念:
① 標籤的出現根本不是做標籤用,而是為了讓JS捕捉執行相關邏輯,最後生成真正的標籤
② 為了做更好的限制,小程式根本不提供入口index.html檔案了,所以這裡的標籤是用作JS做模板解析後生成Native能識別的程式碼,更具體點說是,Native實現了一個元件,元件有很多規則,可以使用JS去呼叫,正如我們這裡的header元件呼叫邏輯(JS會設定Native的Header元件展示),這裡如果不太清晰可以參考下這個文章:淺談Hybrid技術的設計與實現第二彈
當然,小程式底層具體是不是這麼做,我們不得而知,如果有小程式的同事,可以指導下:),至此,我覺得可以從技術層面說明為什麼不直接使用HTML&CSS了:更好的業務限制 + 方便JS解析模板被Native執行。
小程式元件
我們之前做Hybrid應用的時候,事實上只提供了一個真正具有結構的元件Header,其他loading類的提示元件都比較簡單,而我們看看小程式提供了哪些元件呢:
容器類元件
view&scroll-view&swiper等作為容器元件存在,這裡官方有基本介紹,我們這裡看看其中一個即可:
這裡官方給了一個demo進行說明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<view class="section"> <view class="section__title">flex-direction: row</view> <view class="flex-wrp" style="flex-direction:row;"> <view class="flex-item bc_green">1</view> <view class="flex-item bc_red">2</view> <view class="flex-item bc_blue">3</view> </view> </view> <view class="section"> <view class="section__title">flex-direction: column</view> <view class="flex-wrp" style="height: 300px;flex-direction:column;"> <view class="flex-item bc_green">1</view> <view class="flex-item bc_red">2</view> <view class="flex-item bc_blue">3</view> </view> </view> |
1 2 3 4 5 6 7 8 9 |
@import "../lib/weui.wxss"; .page-section{ margin-bottom: 20rpx; } .flex-wrp {display: flex;} .bc_green {background: green;width:100px; height: 100px;} .bc_red {background: red;width:100px; height: 100px;} .bc_blue {background: blue;width:100px; height: 100px;} |
可以將這個標籤理解為div類元件。
swipe
一般來說,Native提供的輪播圖體驗要好得多,所以這裡也提供了一個Native的元件:
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
<view class="container"> <view class="page-body"> <view class="page-section page-section-spacing swiper"> <swiper indicator-dots="{{indicatorDots}}" autoplay="{{autoplay}}" circular="{{circular}}" vertical="{{vertical}}" interval="{{interval}}" duration="{{duration}}" previous-margin="{{previousMargin}}px" next-margin="{{nextMargin}}px"> <block wx:for="{{background}}" wx:key="*this"> <swiper-item> <view class="swiper-item {{item}}"></view> </swiper-item> </block> </swiper> </view> <view class="page-section" style="margin-top: 40rpx;margin-bottom: 0;"> <view class="weui-cells weui-cells_after-title"> <view class="weui-cell weui-cell_switch"> <view class="weui-cell__bd">指示點</view> <view class="weui-cell__ft"> <switch checked="{{indicatorDots}}" bindchange="changeProperty" data-property-name="indicatorDots" /> </view> </view> <view class="weui-cell weui-cell_switch"> <view class="weui-cell__bd">自動播放</view> <view class="weui-cell__ft"> <switch checked="{{autoplay}}" bindchange="changeProperty" data-property-name="autoplay" /> </view> </view> <view class="weui-cell weui-cell_switch"> <view class="weui-cell__bd">銜接滑動</view> <view class="weui-cell__ft"> <switch checked="{{circular}}" bindchange="changeProperty" data-property-name="circular" /> </view> </view> <view class="weui-cell weui-cell_switch"> <view class="weui-cell__bd">豎向</view> <view class="weui-cell__ft"> <switch checked="{{vertical}}" bindchange="changeProperty" data-property-name="vertical" /> </view> </view> </view> </view> <view class="page-section page-section-spacing"> <view class="page-section-title"> <text>幻燈片切換時長(ms)</text> <text class="info">{{duration}}</text> </view> <slider value="{{duration}}" min="500" max="2000" bindchange="changeProperty" data-property-name="duration" /> <view class="page-section-title"> <text>自動播放間隔時長(ms)</text> <text class="info">{{interval}}</text> </view> <slider value="{{interval}}" min="2000" max="10000" bindchange="changeProperty" data-property-name="interval" /> <view class="page-section-title"> <text>前邊距(px)</text> <text class="info">{{previousMargin}}</text> </view> <slider value="{{previousMargin}}" min="0" max="50" bindchange="changeProperty" data-property-name="previousMargin" /> <view class="page-section-title"> <text>後邊距(px)</text> <text class="info">{{nextMargin}}</text> </view> <slider value="{{nextMargin}}" min="0" max="50" bindchange="changeProperty" data-property-name="nextMargin" /> </view> </view> </view> |
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 35 36 37 38 39 |
Page({ data: { background: ['demo-text-1', 'demo-text-2', 'demo-text-3'], indicatorDots: true, vertical: false, autoplay: false, circular: false, interval: 2000, duration: 500, previousMargin: 0, nextMargin: 0 }, changeProperty: function (e) { var propertyName = e.currentTarget.dataset.propertyName var newData = {} newData[propertyName] = e.detail.value this.setData(newData) }, changeIndicatorDots: function (e) { this.setData({ indicatorDots: !this.data.indicatorDots }) }, changeAutoplay: function (e) { this.setData({ autoplay: !this.data.autoplay }) }, intervalChange: function (e) { this.setData({ interval: e.detail.value }) }, durationChange: function (e) { this.setData({ duration: e.detail.value }) } }) |
有demo有程式碼,還是比較清晰。
movable-area
提供一個可以移動的區域,暫時沒想到應用場景……
icon
圖示,小程式這邊還擴充套件了一下,給了很多預設的圖示樣式,能滿足基本需求
text
文字
rich-text
富文字,用於展示文章,支援HTML,這裡的nodes屬性建議使用陣列,型別,還不如系統自己解析js算了,因為不會有人像這樣寫程式碼(nodes看上去很蠢):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Page({ data: { html: '<div class="div_class" style="line-height: 60px; color: red;">Hello World!</div><script>console.log(1)</script>', nodes: [{ name: 'div', attrs: { class: 'div_class', style: 'line-height: 60px; color: red;' }, children: [{ type: 'text', text: 'Hello World!' }] }] }, tap() { console.log('tap') } }) |
progress
進度條
button
按鈕
checkbox
選擇框
form
表單相關
input
輸入框,小程式的資料流動是單向的,每次資料更新,動態呼叫setData改變資料便會觸發view更新,底層實現便不知道了;文字框值改變js需要自己去獲取
label
與html一致,用以點選文字操作控制元件,主要用於文字框
picker&picker-view
用於級聯操作
navigator&function-page-navigator
頁面連結,這個元件感覺不利於跳轉收口,建議少用
其他元件請大家直接到這裡來看demo,非常清晰:
https://developers.weixin.qq.com/miniprogram/dev/component/map.html#map
總結
可以看出,小程式Native層是將常用的HTML標籤分別都實現了一次,使用這些元件可以拼接處任何複雜的元件。至於樣式方面,WXSS與CSS大同小異,其中主要區別是小程式沒有使用px而是使用的rpx,這個類似於rem的實現,為了解決移動端的適配問題而存在,總而言之,你在iPhone6設計搞上是多少px就寫成多少rpx就行,其餘系統會幫你完成適配工作,這塊透明做的很好,後續樣式我們直接上例項即可。
小程式的生命週期
我們這裡上一張圖:
這張圖不但真實反映了Page的生命週期,也將我們之前的猜想做了一個證明,解讀這張圖大概是這個意思(未必正確,如有錯誤請指出):
Native層在載入小程式時候,起了兩個執行緒一個的view Thread一個是AppService Thread,我這邊理解下來應該就是程式邏輯執行與頁面渲染分離,也許是想優化效能,這裡更具體一點的解釋是(帶有猜測了):微信會開一個webview來執行我們的JS邏輯,然後會開一個Native View UI執行頁面渲染;兩個部分是彼此獨立的,頁面點選時候觸發事件,View執行緒會獲取APPService服務執行緒(其實就是獲取webview),執行其中的js邏輯;APPService執行js邏輯改變資料通過setData呼叫,觸發一個JSCore通訊,通知view執行緒執行UI更新,這裡結合這張圖做下理解:
① 微信開啟一個小程式時,主UI執行緒繼續執行,開啟一個webview(我認為這裡的主執行緒就是view Thread,webview就是APPService執行緒,這裡可能有誤)
② 主View等待構建頁面命令,邏輯層開始載入js邏輯(編譯過),微信底層應該會將WXML以及WXSS翻譯為JS程式碼,邏輯層執行JS程式碼做一些初始化工作APP結束後,開始Page邏輯,而他這個圖只有Page的邏輯,沒有將app囊括進去,這裡也引發了我一個疑惑:我在onLoad的時候打了個斷點,而頁面這個時候事實上已經進行了結構層的渲染,也就是說頁面的WXML邏輯已經執行了:
如果要按照我現有的邏輯下做解釋的話,我認為例項化Page的時候,執行了一個create事件,但是小程式並沒有釋放onCreate事件讓我們做註冊,所以我這裡知識體系的基礎依舊是:
1 |
JS邏輯先於Native UI 執行,頁面渲染是由例項化Page時候發出 |
所以我覺得,這裡的圖好像少了一部分(或者說我理解是有問題的):
③ 業務執行緒執行例項化Page邏輯,引發onLoad、onShow事件,onShow的時候頁面初步渲染已經結束,如果系統有非同步資料或者其他再次資料渲染會執行setData,引發Native UI更新,邏輯結束
但是微信給出的圖不可能是錯的,而從圖上看,首次非同步通知是由View Thread發起的,我這裡就很是困惑了?,因為我認為邏輯發起者一定是邏輯層的js發出通知
總結
今天我們對小程式進行了基本的瞭解學習,明天我們持續完成我們的demo吧