現(xiàn)學(xué)現(xiàn)賣微信小程序開發(fā)(一)
現(xiàn)學(xué)現(xiàn)賣微信小程序開發(fā)(二)
現(xiàn)學(xué)現(xiàn)賣微信小程序開發(fā)(三):引入Rx,為小程序插上翅膀
現(xiàn)學(xué)現(xiàn)賣微信小程序開發(fā)
很早就注冊(cè)了微信小程序內(nèi)測(cè),因?yàn)槭峭涎影Y晚期患者,所以一直都沒有學(xué)習(xí)。直到昨天(2016年12月28日),張小龍童鞋宣布微信小程序2017年1月9日正式上線,滿屏都是小程序的消息。我這時(shí)才想起來,我不能白交了300塊錢啊,得學(xué)啊。
反正學(xué)習(xí)也得總結(jié),倒不如現(xiàn)學(xué)現(xiàn)賣寫一個(gè)教程吧。話說回來現(xiàn)學(xué)現(xiàn)賣肯定有不少錯(cuò)誤,大家多包涵哈。廢話少說,我們開工,首先下載微信小程序開發(fā)工具 https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html 選擇自己的平臺(tái)下載安裝即可。

注冊(cè)小程序號(hào)的過程我就不講了(網(wǎng)上這類教程一堆,不用我花時(shí)間在這里。),你要開發(fā)的話,最好通過公司申請(qǐng)一個(gè)。第一次啟動(dòng)開發(fā)工具時(shí)需要掃碼,然后選擇添加一個(gè)無appId的項(xiàng)目。做個(gè)什么項(xiàng)目好呢,沒想好,那就還是先把Todo牽出來吧。我們來實(shí)現(xiàn)一個(gè)微信小程序版的Todo,同樣是帶HTTP后臺(tái)的版本。為什么又選擇Todo呢?因?yàn)樗倪壿嬒鄬?duì)完整,增刪改查都有,HTTP請(qǐng)求、返回俱全。在一個(gè)平臺(tái)折騰明白這個(gè)app就基本可以上手干活了,這貨簡直就是新時(shí)代的Hello World啊。
文件結(jié)構(gòu)
新建工程時(shí),默認(rèn)開發(fā)工具會(huì)生成一個(gè)樣例給我們。我們就來看看這個(gè)樣例是什么東東。開發(fā)工具的左側(cè)是一個(gè)預(yù)覽,中間那欄是文件目錄,看目錄結(jié)構(gòu)倒是蠻簡單:

全局文件
先來看一下根目錄:這個(gè)目錄下有三個(gè)文件app.js,app.json和app.wxss。app.js是應(yīng)用入口,里面定義了App() 函數(shù),用來注冊(cè)一個(gè)小程序,就像Android里面的Application和Angular2中的。接受一個(gè) object 參數(shù),其指定小程序的生命周期函數(shù)等。
//app.js
App({
onLaunch: function () {
//調(diào)用API從本地緩存中獲取數(shù)據(jù)
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
},
getUserInfo:function(cb){
var that = this
if(this.globalData.userInfo){
typeof cb == "function" && cb(this.globalData.userInfo)
}else{
//調(diào)用登錄接口
wx.login({
success: function () {
wx.getUserInfo({
success: function (res) {
that.globalData.userInfo = res.userInfo
typeof cb == "function" && cb(that.globalData.userInfo)
}
})
}
})
}
},
globalData:{
userInfo:null
}
})
從上面的例子代碼看的話,雖然簡單還是能看出一些東西的:
- App()本身就是一個(gè)函數(shù),在其他頁面可以通過系統(tǒng)提供的getApp()來得到應(yīng)用的實(shí)例。
- onLaunch就是一個(gè)生命周期函數(shù),是當(dāng)小程序初始化完成時(shí),會(huì)觸發(fā)這個(gè)函數(shù)(只觸發(fā)一次)。這里在應(yīng)用初始化時(shí)向本地存儲(chǔ)寫入了一個(gè)日期。
- globalData是個(gè)全局?jǐn)?shù)據(jù)的存放區(qū)域,示例代碼中只放置了一個(gè)userInfo作為全局變量。
- 例子代碼在這里定義了一個(gè)全局方法getUserInfo,這個(gè)全局方法的訪問可以通過getApp().getUserInfo()。如果你打開
pages/index/index.js,你會(huì)發(fā)現(xiàn)示例代碼通過getApp()取得應(yīng)用實(shí)例后調(diào)用了getUserInfo這個(gè)全局方法
//獲取應(yīng)用實(shí)例
const app = getApp()
...
//調(diào)用應(yīng)用實(shí)例的方法獲取全局?jǐn)?shù)據(jù)
app.getUserInfo(function(userInfo){
//更新數(shù)據(jù)
that.setData({
userInfo:userInfo
})
})
那么類似像onLaunch這樣的生命周期函數(shù)有哪些呢?還有onShow,onHide和onError。onShow和onHide分別是應(yīng)用從后臺(tái)切換到前臺(tái)以及從前臺(tái)進(jìn)入后臺(tái)時(shí)調(diào)用的。當(dāng)你關(guān)閉小程序或者切換微信應(yīng)用到別的應(yīng)用時(shí),微信并沒有直接干掉小程序,小程序只是進(jìn)入后臺(tái)而已,當(dāng)你切換回來,小程序就又進(jìn)入了前臺(tái)。這個(gè)機(jī)制其實(shí)和Android以及iOS的應(yīng)用切換很像。onError顧名思義就是小程序發(fā)生腳本錯(cuò)誤或者api調(diào)用失敗是觸發(fā)的。注意:請(qǐng)不要在其他頁面調(diào)用生命周期函數(shù)
接下來我們看看 app.json,這個(gè)東東呢,就是小程序的應(yīng)用全局配置文件。結(jié)構(gòu)也很簡單:pages定義頁面的文件路徑,window定義小程序整個(gè)窗口的一些元素,比如Title的文字,Title的顏色,窗口背景顏色等等。
{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "todos",
"navigationBarTextStyle":"black"
},
"debug": true
}
比如在上面的配置下,窗口的展現(xiàn)形式是這樣的

如果我們改動(dòng)app.json 成為下面的樣子,那么是什么效果呢?
{
"pages":[
"pages/index/index",
"pages/todos/todos"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#000",
"navigationBarTitleText": "todo",
"navigationBarTextStyle":"white",
"backgroundColor": "#492b2b"
},
"debug": true
}

嗯,導(dǎo)航欄背景變成黑色了,文字白色,一切很完美。且慢,我們不是還添加了一個(gè) "backgroundColor": "#492b2b" 嗎,它的效果怎么沒有出來呢?你點(diǎn)擊頭像進(jìn)入log頁面后,再點(diǎn)擊左上角返回會(huì)看到一個(gè)動(dòng)畫,這個(gè)動(dòng)畫的背景竟然是我們?cè)O(shè)置的窗口背景色???

但為毛在靜態(tài)界面上看不到呢,我猜想是由于Page擋在了背景前面,但是不是呢,我們來用試驗(yàn)驗(yàn)證一下。怎么驗(yàn)證呢,我們?cè)趙indow的設(shè)置中再加一個(gè)配置 "enablePullDownRefresh": "true",這個(gè)是激活下拉刷新的效果的開關(guān),我們?cè)囈幌?,拖住界面往下拉,果然,窗口背景色在后面藏著呢?/p>

同樣 backgroundTextStyle定義的是下拉背景字體、loading 圖的樣式(僅支持 dark/light)
在這個(gè)配置文件中,我們還可以定義用于切換界面的工具欄(tab bar),我們也來試驗(yàn)一下吧。
{
"pages":[
"pages/index/index",
"pages/todos/todos"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#000",
"navigationBarTitleText": "todo",
"navigationBarTextStyle":"white",
"backgroundColor": "#492b2b",
"enablePullDownRefresh": "true"
},
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首頁"
}, {
"pagePath": "pages/logs/logs",
"text": "日志"
}]
},
"debug": true
}

嗯嗯,很丑陋,但是畢竟顯示出來了。那么問題來了,這個(gè)tabBar如何自定義呢?除了list屬性,它還提供color,selectedColor,backgroundColor,borderStyle和postion屬性。我們都設(shè)置一下
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首頁",
"selectedIconPath": "assets/images/home.png"
}, {
"pagePath": "pages/logs/logs",
"text": "日志",
"iconPath": "assets/images/log.png"
}],
"color": "#b0bec5",
"backgroundColor": "#2196F3",
"selectedColor": "#fff",
"borderStyle": "#F8BBD0",
"position": "bottom"
}

還是比較丑,碼農(nóng)加直男,只能湊活了。你可能發(fā)現(xiàn)我在list中一個(gè)添加的是selectedIconPath,一個(gè)添加的是iconPath。其實(shí)我是犯懶,不想再找兩個(gè)圖片而已。恰好首頁是默認(rèn)選中的tab,那么正好兩個(gè)圖標(biāo)都能顯示出來。
除了tabBar,還可以設(shè)置一個(gè)debug屬性,默認(rèn)是false,如果設(shè)置成true,那么你在調(diào)試的時(shí)候,會(huì)發(fā)現(xiàn)在console里面顯示很多l(xiāng)og信息。其信息有Page的注冊(cè),頁面路由,數(shù)據(jù)更新,事件觸發(fā)等。這個(gè)開關(guān)在開發(fā)時(shí)建議打開,在發(fā)布時(shí)記著關(guān)閉哈。

再來看 app.wxss,估計(jì)你猜也猜得到,這個(gè)就是類似css的樣式表了。這個(gè)全局樣式所有頁面都可以使用。
/**app.wxss**/
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}
像我這么愛折騰的人,對(duì)于這個(gè)css肯定要鼓搗一通,看看到底起什么作用?我們首先加一個(gè)背景色,紅色比較顯眼,background-color: red;。

首頁下方有一片區(qū)域沒有著色,先不管它,點(diǎn)頭像進(jìn)去看看。

這種變化說明這個(gè)container選擇器是應(yīng)用于頁面的,根據(jù)頁面內(nèi)容來決定高度,所以這個(gè)100%并不能填滿全屏。好在微信小程序可以接受css的大部分設(shè)置,如果把 height: 100%; 改成 height: 100vh; 就可以填滿全屏了。
頁面文件
頁面文件也很簡單,基本就是和文件夾同名的 .js、 .json、 .wxss 文件,唯一多了一種類型就是 .wxml 文件。.js文件中定義一個(gè)Page函數(shù),用于構(gòu)建頁面視圖的邏輯,而.wxml描述頁面視圖的結(jié)構(gòu)和布局。首先看一下index.js:
var app = getApp()
Page({
data: {
motto: 'Hello World',
userInfo: {}
},
//事件處理函數(shù)
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
console.log('onLoad')
var that = this
//調(diào)用應(yīng)用實(shí)例的方法獲取全局?jǐn)?shù)據(jù)
app.getUserInfo(function(userInfo){
//更新數(shù)據(jù)
that.setData({
userInfo:userInfo
})
})
}
})
由上面的結(jié)構(gòu)可以看出基本上Page函數(shù)是一個(gè)這樣的形式。它接受一個(gè)對(duì)象,這個(gè)對(duì)象有一些生命周期函數(shù)、事件處理函數(shù)和數(shù)據(jù)對(duì)象構(gòu)成。也就是說上面的代碼可以改寫成下面這樣的形式。
let app = getApp()
let pageParams = {
data: {
motto: 'Hello World',
userInfo: {}
},
//事件處理函數(shù)
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
console.log('onLoad')
var that = this
//調(diào)用應(yīng)用實(shí)例的方法獲取全局?jǐn)?shù)據(jù)
app.getUserInfo(function(userInfo){
//更新數(shù)據(jù)
that.setData({
userInfo:userInfo
})
})
}
}
Page(pageParams)
事實(shí)上我們更傾向于這么寫,因?yàn)檫@樣的寫法可以使我們避免太多嵌套,以及對(duì)pageParams做更多靈活的操作。因?yàn)槭莻€(gè)對(duì)象,我們可以寫成 pageParams.data = blablabla 或 pageParams.onLoad = someFunctionCall。對(duì)了,這個(gè)對(duì)象我也不知道叫什么,自己給它起的名字而已,反正Page函數(shù)是需要這么一個(gè)參數(shù)的。
那么頁面的內(nèi)建函數(shù)有哪些呢?可以參照下表,其中前5項(xiàng)屬于生命周期函數(shù)。
| 內(nèi)建函數(shù) | 作用 | 調(diào)用機(jī)制 |
|---|---|---|
| onLoad | 監(jiān)聽頁面加載 | 生命周期內(nèi)調(diào)用一次 |
| onReady | 監(jiān)聽頁面初次渲染完成 | 生命周期內(nèi)調(diào)用一次 |
| onShow | 監(jiān)聽頁面顯示 | 每次打開頁面都會(huì)調(diào)用一次 |
| onHide | 監(jiān)聽頁面隱藏 | 當(dāng)navigateTo或底部tab切換時(shí)調(diào)用 |
| onUnload | 監(jiān)聽頁面卸載 | 當(dāng)redirectTo或navigateBack的時(shí)候調(diào)用 |
| onPullDownRefresh | 監(jiān)聽用戶下拉動(dòng)作 | 用戶動(dòng)作觸發(fā) |
| onReachBottom | 頁面上拉觸底事件的處理函數(shù) | 事件觸發(fā) |
| onShareAppMessage | 用戶點(diǎn)擊右上角分享 | 用戶點(diǎn)擊觸發(fā) |
具體可以參看微信官方文檔給出的頁面生命周期示意圖

再來看看 index.wxml,其實(shí)就是類似html的一個(gè)模版文件,但是微信自己重造輪子的目的不是很清楚。這里 class="container" 應(yīng)用了我們的全局樣式。
<!--index.wxml-->
<view class="container">
<view bindtap="bindViewTap" class="userinfo">
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>
事件綁定是通過在組件標(biāo)簽內(nèi)加 bindXXX=handler 來完成的,這個(gè) handler 需要在pageParams對(duì)象中定義。 {{userInfo.nickName}} 是引用了我們?cè)?index.js 中定義的數(shù)據(jù)對(duì)象的屬性。
index.json 也是蠻簡單,就是設(shè)置頁面的一些屬性,比如我們改寫一下的話:
{
"navigationBarTitleText": "首頁"
}
導(dǎo)航欄文字就會(huì)變成我們?cè)O(shè)置的值。

好的,到現(xiàn)在我們應(yīng)該清楚了大概的微信小應(yīng)用的基礎(chǔ)知識(shí)。下一節(jié)我們會(huì)嘗試著做我們的Todo應(yīng)用了。