本期想要和大家分享一下大眾點(diǎn)評(píng)點(diǎn)餐小程序開(kāi)發(fā)中的邏輯層的經(jīng)驗(yàn)。與視圖層微信自己定義了一套與HTML對(duì)應(yīng)的WXML和WXSS不同,小程 ...
本期想要和大家分享一下大眾點(diǎn)評(píng)點(diǎn)餐小程序開(kāi)發(fā)中的邏輯層的經(jīng)驗(yàn)。
與視圖層微信自己定義了一套與HTML對(duì)應(yīng)的WXML和WXSS不同,小程序的邏輯層還是使用javascript編寫(xiě)的。不過(guò)與我們普通的編寫(xiě)js還是有一些區(qū)別的。接下來(lái)我會(huì)根據(jù)實(shí)踐進(jìn)行說(shuō)明。邏輯層代碼結(jié)構(gòu)為
menu
├── menu.html
├── menu.js
├── menu.json
└── menu.less
app.js
作為邏輯層我們只需要關(guān)注app.js和menu.js。
小程序提供了App方法來(lái)注冊(cè)整個(gè)小程序,在App方法里我們可以傳入一個(gè)對(duì)象,指定小程序的生命周期函數(shù)以及自定義的函數(shù)或者數(shù)據(jù)。注意這個(gè)函數(shù)只能被調(diào)用一次。
如上所示,App擁有著4個(gè)生命周期函數(shù),我們可以在launch的時(shí)候進(jìn)行一些全局信息的獲取,比如用戶信息,門(mén)店信息等等,然后存入到全局?jǐn)?shù)據(jù)中。這里的數(shù)據(jù)可以被每個(gè)頁(yè)面訪問(wèn)到。
小程序針對(duì)每個(gè)頁(yè)面提供了Page的函數(shù)。整個(gè)邏輯層大部分的代碼都會(huì)寫(xiě)在Page函數(shù)中,Page中承接著整個(gè)頁(yè)面的數(shù)據(jù),生命周期函數(shù),以及在視圖中綁定的事件的觸發(fā)函數(shù),例如各點(diǎn)擊事件。整個(gè)Page函數(shù)允許的參數(shù)如下所示:
如上,Page函數(shù)因?yàn)槭琼?yè)面級(jí)別的,所以擁有著更多的生命函數(shù),會(huì)有下拉刷新事件,會(huì)有頁(yè)面到達(dá)底部的事件。這里我們需要區(qū)別好各個(gè)生命周期函數(shù)。onLoad只會(huì)在初始化的時(shí)候調(diào)用一次,onShow是每次打開(kāi)頁(yè)面都會(huì)調(diào)用,onReady只有頁(yè)面初次渲染完成才會(huì)被調(diào)用。onHide會(huì)在navigateTo(微信提供的跳轉(zhuǎn)API)或者底部tab切換時(shí)調(diào)用,onUnload會(huì)在redirectTo(微信提供的redirect的API)或者navigateBack(微信提供的回退的API)的時(shí)候調(diào)用。Page更具體的渲染過(guò)程可以參考下面這張圖:
簡(jiǎn)單描述下就是:視圖層和邏輯層同時(shí)進(jìn)行初始化的操作:視圖層ready之后通知邏輯層發(fā)送數(shù)據(jù);邏輯層執(zhí)行onload和onShow方法,然后等待視圖層的通知,在接收到視圖層的通知之后發(fā)送數(shù)據(jù)給視圖層,然后繼續(xù)等待視圖層的通知。視圖層根據(jù)數(shù)據(jù)進(jìn)行初次渲染后通知邏輯層渲染完畢,邏輯層調(diào)用onReady方法。然后后續(xù)的行為邏輯層可以通過(guò)再次發(fā)送數(shù)據(jù)重新渲染視圖層。
Page的整個(gè)工作流程可以參照下面的圖:
小程序內(nèi)申明的變量和函數(shù)只在該文件內(nèi)有效,不同的文件可以申明相同名字的變量和函數(shù),并不會(huì)相互影響。上面提到App內(nèi)可以設(shè)置全局?jǐn)?shù)據(jù)。我們?cè)诿總€(gè)Page里面都可以通過(guò)全局函數(shù)getApp()來(lái)拿到全局的引用實(shí)例。然后就可以訪問(wèn)頁(yè)面的數(shù)據(jù)。比如我們?cè)谫?gòu)物車(chē)下完單之后回到菜單頁(yè)可能會(huì)需要進(jìn)行菜單的刷新,我們?cè)谫?gòu)物車(chē)頁(yè)面就會(huì)調(diào)用getApp().data.menuRefresh = true,然后在菜單頁(yè)的onShow方法進(jìn)行判斷,例如:
let app = getApp();
Page(
requestMenu () {
//刷新菜單
};
onShow () {
if (app.data.menuRefresh === true) {
app.data.menuRefresh === false;
this.requestMenu();
}
}
);
在每個(gè)Page內(nèi),我們還可以用getCurrentPages來(lái)獲取當(dāng)前頁(yè)面棧的實(shí)例,數(shù)組形式,第一個(gè)元素為首頁(yè),最后一個(gè)元素為當(dāng)前頁(yè)面。頁(yè)面棧的表現(xiàn)情況如下表所示:
路由方式 | 頁(yè)面棧表現(xiàn) |
---|---|
初始化 | 新頁(yè)面入棧 |
打開(kāi)新頁(yè)面 | 新頁(yè)面入棧 |
頁(yè)面重定向 | 當(dāng)前頁(yè)面出棧,新頁(yè)面入棧 |
頁(yè)面返回 | 頁(yè)面不斷出棧,直到目標(biāo)返回頁(yè),新頁(yè)面入棧 |
Tab切換 | 頁(yè)面全部出棧,只留下新的Tab頁(yè)面 |
注意我們不能手動(dòng)去嘗試修改頁(yè)面棧,我們只能根據(jù)頁(yè)面棧,來(lái)分析是使用哪種微信的API來(lái)跳頁(yè)面。這里的跳轉(zhuǎn)API還會(huì)在下面進(jìn)行講解。
小程序是支持模塊化的,支持commonjs的模塊化寫(xiě)法,也就是module.exports或者exports,這兩個(gè)的區(qū)別這里就不細(xì)講了,不了解的可以去看下nodejs的module那塊的文檔。小程序目前并不支持引入node_modules,也就是并不支持第三方的模塊,當(dāng)我們需要使用到外部的依賴(lài)的時(shí)候,建議直接將代碼拷貝到小程序的目錄中,然后通過(guò)相對(duì)路徑的require函數(shù)進(jìn)行引入。
小程序作為微信的一個(gè)重要功能,微信的框架提供了非常豐富的微信原生API,可以方便的調(diào)起微信提供的能力,除了視圖層的一些原生組件外,還有一些功能性的API,如掃碼,定位,媒體播放,本地存儲(chǔ)以及支付功能等等。
我們這次使用的較多的是通過(guò)微信發(fā)起網(wǎng)絡(luò)請(qǐng)求以及微信的數(shù)據(jù)存儲(chǔ)。
微信提供了wx.request來(lái)發(fā)起請(qǐng)求,注意這個(gè)方法發(fā)起的是HTTPS請(qǐng)求。所以在開(kāi)發(fā)微信小程序之前,大家得先遷一下HTTPS~我們自己在使用API的時(shí)候,還用了pinkie這個(gè)包將request包裝成了Promise的形式方便我們使用。
比較重要的一點(diǎn)是微信的運(yùn)行環(huán)境并不是瀏覽器,并不提供cookie的功能。但是用戶我們解決用戶鑒別的問(wèn)題是帶上用戶的token,用戶的token是在用戶登錄的時(shí)候后端生成好了放置到App的全局?jǐn)?shù)據(jù)中。
我們大眾點(diǎn)評(píng)點(diǎn)餐頁(yè)面上有大量的菜單數(shù)據(jù),這部分?jǐn)?shù)據(jù)之前在H5上實(shí)現(xiàn)的時(shí)候用的是瀏覽器的localstorage。這次切換到微信的storage,代價(jià)很小,用了一下適配器模式,將微信的數(shù)據(jù)接口適配成我們需要的接口就好了。這樣也是為了以后的迭代慢慢讓H5與小程序使用同一套代碼。
小程序?yàn)榱藴p少用戶使用的時(shí)候的困擾,規(guī)定了頁(yè)面路徑最多只能有5層,所以我們使用的時(shí)候得盡量避免多層級(jí)的交互方式。
const app = getApp();
module.exports = function go2Page(opts) {
if (!opts) return;
if (!opts.url) return;
let url = opts.url;
//拿到當(dāng)前的頁(yè)面棧
const history = getCurrentPages();
let path = url.split('?')
let params;
if (path.length === 2) {
params = path[1];
}
let page = path[0].split('/').pop();
let index = -1;
for (var i = 0; i < history.length; i++) {
let hPath = history[i].__route__;
let hPage = hPath.split('/').pop();
if (page == hPage) {
index = i;
break;
}
}
if (index === -1) {
//如果不存在這個(gè)頁(yè)面,直接跳轉(zhuǎn)
wx.navigateTo({
url: url
});
} else {
//如果存在這個(gè)頁(yè)面,就回退回去
if (params) {
//query是處理下url參數(shù)的自己定義的函數(shù)
params = query(params);
}
//將跳轉(zhuǎn)的頁(yè)面的參數(shù)保存到全局?jǐn)?shù)據(jù)中,然后在頁(yè)面中可以去拿取,store是自己申明的
app.store(page, params);
wx.navigateBack({
delta: history.length - (index + 1)
});
}
}
由于小程序的框架并非運(yùn)行在瀏覽器中,所以javascript在web端的一些能力都無(wú)法使用,除了上面提到的cookie,還有document,window等等。開(kāi)發(fā)者所有代碼最終會(huì)被打包成一份javascript,在小程序啟動(dòng)的時(shí)候運(yùn)行,直到小程序銷(xiāo)毀。這一點(diǎn)類(lèi)似于瀏覽器的ServiceWorker,所以邏輯層也稱(chēng)之為App Service。
本文時(shí)間為2017-03-02,后續(xù)更新或修復(fù)請(qǐng)查看官方文檔。