最近做了一個(gè)投票的微信小程序,開發(fā)過(guò)程主要還是參考官方文檔:https://mp.weixin.qq.com/debug/wxadoc/dev/ 由于一直是做 Android 開發(fā)的审洞,所以寫小程序界面時(shí)還需要大概看一下前端的東西忍啤,對(duì)于這些東西微信自己也做了一些封裝,但總體來(lái)說(shuō)差別不大赞季,這里進(jìn)行一下總結(jié)。
注冊(cè)并創(chuàng)建項(xiàng)目
登陸 https://mp.weixin.qq.com 進(jìn)行注冊(cè),獲取 AppID并在設(shè)置中進(jìn)行一些配置(如服務(wù)器域名)展氓,下載小程序的開發(fā)工具,掃描二維碼登陸脸爱,創(chuàng)建新項(xiàng)目遇汞。在創(chuàng)建項(xiàng)目時(shí)可以選擇創(chuàng)建一個(gè) quick start 項(xiàng)目,這樣會(huì)自動(dòng)生成一個(gè)簡(jiǎn)單的 demo簿废,有助于我們了解項(xiàng)目的結(jié)構(gòu)和組成空入。
但是請(qǐng)注意,坑爹的小程序是不支持個(gè)人用戶注冊(cè)的族檬,所以如果不是公司項(xiàng)目歪赢,而只是個(gè)人想體驗(yàn)一下,要么在創(chuàng)建項(xiàng)目時(shí)選擇無(wú) AppID单料,但這樣就只能在開發(fā)工具的模擬器上運(yùn)行埋凯,不能在手機(jī)上運(yùn)行也不能發(fā)布点楼,要么參考 https://zhuanlan.zhihu.com/p/24810538 這篇文章鉆個(gè)漏洞獲取一個(gè)AppID,當(dāng)然還是沒(méi)辦法發(fā)布递鹉,但至少能在手機(jī)上運(yùn)行了盟步。
框架
小程序的框架分為視圖層(View)和邏輯層(App Service),它提供了自己的視圖層描述語(yǔ)言 WXML 和 WXSS躏结,以及基于 JavaScript 的邏輯層框架却盘,并在視圖層與邏輯層間提供了數(shù)據(jù)傳輸和事件系統(tǒng)。
這里的 WXML 和 WXSS 類似于前端的 HTML 和 CSS媳拴,但是 WXML 只能使用微信自己定義的組件而不能使用 HTML 里面的標(biāo)簽黄橘,WXSS 則和 CSS 無(wú)太大差別。
框架的核心是一個(gè)響應(yīng)的數(shù)據(jù)綁定系統(tǒng)屈溉,也就是說(shuō)當(dāng)做數(shù)據(jù)修改的時(shí)候塞关,只需要在邏輯層修改數(shù)據(jù),視圖層就會(huì)做相應(yīng)的更新子巾。
代碼結(jié)構(gòu)
上圖是我做的投票小程序里面的代碼結(jié)構(gòu):
1帆赢、一個(gè)小程序主體部分由 app.js、app.json线梗、app.wxss 三個(gè)文件組成椰于,必須放在項(xiàng)目的根目錄,分別是整個(gè)程序的邏輯仪搔、全局配置及樣式瘾婿。
(1) app.js 是小程序的腳本代碼。通過(guò)App()
函數(shù)用來(lái)注冊(cè)一個(gè)小程序烤咧,接受一個(gè) Object 參數(shù)偏陪,指定小程序的生命周期函數(shù)等,如下圖所示煮嫌。
類似于 Android 中 的 Application笛谦,我們可以在這個(gè)文件中監(jiān)聽(tīng)并處理小程序的生命周期函數(shù)、聲明全局變量昌阿。
其他地方使用時(shí)通過(guò)var app = getApp()
即可獲取其實(shí)例饥脑,并調(diào)用其中定義的方法和變量,但不要調(diào)用生命周期的方法宝泵。在App()
的外面還可以另外定義 function 和變量好啰,但只能在本文件內(nèi)使用轩娶。
(2) app.json 是對(duì)整個(gè)小程序的全局配置儿奶。我們可以在這個(gè)文件中配置小程序是由哪些頁(yè)面組成,配置小程序的窗口背景色鳄抒,配置導(dǎo)航條樣式闯捎,配置默認(rèn)標(biāo)題椰弊。
pages
指定了小程序的組成頁(yè)面,第一個(gè)代表小程序的初始頁(yè)面瓤鼻。
window
用于設(shè)置小程序的狀態(tài)欄秉版、導(dǎo)航條、標(biāo)題茬祷、窗口背景色清焕。
tabBar
用于配置客戶端窗口的底部或頂部 tab 欄的樣式以及 tab 切換時(shí)顯示的對(duì)應(yīng)頁(yè)面。
另外還可以配置各種網(wǎng)絡(luò)請(qǐng)求的超時(shí)時(shí)間networkTimeout
和是否開啟調(diào)試模式debug
祭犯。
(3) app.wxss 是整個(gè)小程序的公共樣式表秸妥。可以配置一些通用的樣式沃粗。
2粥惧、pages 里面則是小程序的各個(gè)頁(yè)面,其中 index 一般作為主界面(當(dāng)然這并不是由名字決定的最盅,而是在app.json里面配置的第一個(gè)page)突雪,可以看到,一個(gè)界面由 wxml涡贱、wxss咏删、js、json 等四個(gè)文件組成盼产,分別是頁(yè)面的邏輯饵婆、界面結(jié)構(gòu),樣式以及配置戏售。小程序規(guī)定這四個(gè)文件必須具有相同的路徑和名字侨核。
(1) js 是頁(yè)面的腳本代碼。通過(guò)Page()
函數(shù)用來(lái)注冊(cè)一個(gè)頁(yè)面灌灾。接受一個(gè) Object 參數(shù)搓译,其指定頁(yè)面的初始數(shù)據(jù)、生命周期函數(shù)锋喜、事件處理函數(shù)等些己,如下圖所示。
其中data
定義了頁(yè)面的初始數(shù)據(jù)嘿般,會(huì)以 JSON 的形式由邏輯層傳至渲染層段标,所以其數(shù)據(jù)必須是可以轉(zhuǎn)成 JSON 的格式:字符串,數(shù)字炉奴,布爾值逼庞,對(duì)象,數(shù)組瞻赶。渲染層可以通過(guò) WXML 對(duì)數(shù)據(jù)進(jìn)行綁定赛糟。
onLoad派任、onShow、onReady璧南、onHide掌逛、onUnload
是頁(yè)面的生命周期函數(shù),分別在頁(yè)面加載司倚、顯示豆混、初次渲染完成、隱藏和卸載時(shí)調(diào)用动知。其中onLoad
和onReady
只會(huì)在頁(yè)面加載時(shí)調(diào)用一次崖叫,onShow
則每次顯示頁(yè)面都會(huì)調(diào)用一次。
onPullDownRefresh
用于監(jiān)聽(tīng)用戶下拉刷新事件拍柒,需要在 json 配置文件中開啟enablePullDownRefresh
心傀。當(dāng)處理完數(shù)據(jù)刷新后,wx.stopPullDownRefresh
可以停止當(dāng)前頁(yè)面的下拉刷新拆讯。
onShareAppMessage
只有定義了該方法才會(huì)在微信的右上角菜單顯示分享按鈕脂男,需要 return 一個(gè) Object,用于自定義分享內(nèi)容种呐,包括title
標(biāo)題和path
分享的頁(yè)面的完整路徑宰翅。
viewTap
是事件處理函數(shù),函數(shù)名是自己取的爽室,在渲染層可以在組件中加入事件綁定<view bindtap="viewTap"> click me </view>
汁讼,當(dāng)達(dá)到觸發(fā)事件時(shí),就會(huì)執(zhí)行 Page 中定義的事件處理函數(shù)阔墩。
當(dāng)需要改變data
中的數(shù)據(jù)時(shí)嘿架,不能直接修改this.data
,而需要調(diào)用this.setData()
方法進(jìn)行修改啸箫。
在Page()
的外面同樣可以另外定義 function 和變量耸彪,也只能在本文件內(nèi)使用。
(2) wxml 是頁(yè)面的布局文件忘苛,只能使用微信自己定義的組件蝉娜。https://mp.weixin.qq.com/debug/wxadoc/dev/component/ 這里是微信提供的所有組件的列表和屬性,其中使用得最多的是 view 以及一些表單組件如 button 等等扎唾。具體的布局方式跟HTML差不多召川,這里不再多說(shuō)。
(3) wxss 是樣式表胸遇,具有 CSS 大部分特性荧呐,并進(jìn)行了特性擴(kuò)展,主要包括:
① 尺寸單位:rpx(responsive pixel)可以根據(jù)屏幕寬度進(jìn)行自適應(yīng)。規(guī)定屏幕寬為750rpx坛增。如在 iPhone6 上,屏幕寬度為375px薄腻,共有750個(gè)物理像素收捣,則750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素庵楷。
② 樣式導(dǎo)入:使用@import語(yǔ)句可以導(dǎo)入外聯(lián)樣式表罢艾,@import后跟需要導(dǎo)入的外聯(lián)樣式表的相對(duì)路徑,用;表示語(yǔ)句結(jié)束尽纽。例如:
/** common.wxss **/
.small-p {
padding:5px;
}
/** app.wxss **/
@import "common.wxss";
.middle-p {
padding:15px;
}
定義在 app.wxss 中的樣式為全局樣式咐蚯,作用于每一個(gè)頁(yè)面。在 page 的 wxss 文件中定義的樣式為局部樣式弄贿,只作用在對(duì)應(yīng)的頁(yè)面春锋,并會(huì)覆蓋 app.wxss 中相同的選擇器。
(4) json 是頁(yè)面的配置文件差凹,頁(yè)面的配置比app.json全局配置簡(jiǎn)單得多期奔,只是設(shè)置 app.json 中的 window 配置項(xiàng)的內(nèi)容,頁(yè)面中配置項(xiàng)會(huì)覆蓋 app.json 的 window 中相同的配置項(xiàng)危尿,無(wú)需寫 window 這個(gè)鍵呐萌。上面說(shuō)的開啟下拉刷新功能就需要在這個(gè)文件里面進(jìn)行配置:
由于是 json 格式的文件,即使不需要配置任何東西也需要寫{}
谊娇,否則會(huì)報(bào)錯(cuò)肺孤。
3、utils 里面包含了一些將公共的代碼抽離出來(lái)的 js 文件济欢,作為一個(gè)模塊可以方便被任何地方使用赠堵。模塊只有通過(guò) module.exports 才能對(duì)外暴露接口。下圖即為utils/util.js文件中的內(nèi)容法褥,包含了一個(gè)formatDate的方法顾腊,并將方法 exports 給外部使用。
在其他地方使用時(shí)需要通過(guò)var utils = require('../../utils/util.js');
進(jìn)行引用挖胃,之后就可以通過(guò)變量 utils 調(diào)用 util.js 文件中定義的方法杂靶。
4、images 里面則放了一些圖片資源酱鸭。
數(shù)據(jù)綁定
WXML 中的動(dòng)態(tài)數(shù)據(jù)均來(lái)自對(duì)應(yīng) Page 的 data吗垮。數(shù)據(jù)綁定使用雙大括號(hào)將變量包起來(lái),可以作用于內(nèi)容凹髓、組件屬性(需要在雙引號(hào)之內(nèi))烁登、控制屬性(需要在雙引號(hào)之內(nèi))、關(guān)鍵字(需要在雙引號(hào)之內(nèi))。
例如:
Page({
data: {
message: "Hello",
id: 0,
condition: true
}
})
<view> {{message}} </view>
<view id="item-{{id}}"> </view>
<view wx:if="{{condition}}"> </view>
<checkbox checked="{{condition}}"> </checkbox>
還可以在 {{}} 內(nèi)進(jìn)行簡(jiǎn)單的運(yùn)算饵沧,如:
Page({
data: {
flag: true,
a: 1,
b: 2,
c: 3
length: 6,
name: 'MINA',
object: {
key: 'Hello '
},
array: ['MINA']
}
})
<view hidden="{{flag ? true : false}}"> Hidden </view>
<view> {{a + b}} + {{c}} + d </view> // 結(jié)果為3 + 3 + d
<view wx:if="{{length > 5}}"> </view>
<view>{{"hello" + name}}</view>
<view>{{object.key}} {{array[0]}}</view>
條件渲染
在框架中锨络,我們用 wx:if="{{condition}}" 來(lái)判斷是否需要渲染該代碼塊:
<view wx:if="{{condition}}"> True </view>
也可以用 wx:elif 和 wx:else 來(lái)添加一個(gè) else 塊:
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
因?yàn)?wx:if 是一個(gè)控制屬性,需要將它添加到一個(gè)標(biāo)簽上狼牺。但是如果我們想一次性判斷多個(gè)組件標(biāo)簽羡儿,我們可以使用一個(gè) <block/> 標(biāo)簽將多個(gè)組件包裝起來(lái),并在上邊使用 wx:if 控制屬性是钥。
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
注意: <block/> 并不是一個(gè)組件掠归,它僅僅是一個(gè)包裝元素,不會(huì)在頁(yè)面中做任何渲染悄泥,只接受控制屬性虏冻。
一般來(lái)說(shuō),wx:if 有更高的切換消耗而 hidden 有更高的初始渲染消耗弹囚。因此厨相,如果需要頻繁切換的情景下,用 hidden 更好鸥鹉,如果在運(yùn)行時(shí)條件不大可能改變則 wx:if 較好领铐。
列表渲染
在組件上使用wx:for控制屬性綁定一個(gè)數(shù)組,即可使用數(shù)組中各項(xiàng)的數(shù)據(jù)重復(fù)渲染該組件宋舷。
默認(rèn)數(shù)組的當(dāng)前項(xiàng)的下標(biāo)變量名默認(rèn)為index绪撵,數(shù)組當(dāng)前項(xiàng)的變量名默認(rèn)為item
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
使用 wx:for-item 可以指定數(shù)組當(dāng)前元素的變量名,使用 wx:for-index 可以指定數(shù)組當(dāng)前下標(biāo)的變量名:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
wx:for也可以嵌套祝蝠,下邊是一個(gè)九九乘法表
<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i">
<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j">
<view wx:if="{{i <= j}}">
{{i}} * {{j}} = {{i * j}}
</view>
</view>
</view>
類似block wx:if音诈,也可以將wx:for用在<block/>標(biāo)簽上,以渲染一個(gè)包含多節(jié)點(diǎn)的結(jié)構(gòu)塊绎狭。例如:
<block wx:for="{{[1, 2, 3]}}">
<view> {{index}}: </view>
<view> {{item}} </view>
</block>
如果列表中項(xiàng)目的位置會(huì)動(dòng)態(tài)改變或者有新的項(xiàng)目添加到列表中细溅,并且希望列表中的項(xiàng)目保持自己的特征和狀態(tài)(如 <input/> 中的輸入內(nèi)容,<switch/> 的選中狀態(tài))儡嘶,需要使用 wx:key 來(lái)指定列表中項(xiàng)目的唯一的標(biāo)識(shí)符喇聊。wx:key 的值以兩種形式提供:
① 字符串,代表在 for 循環(huán)的 array 中 item 的某個(gè) property蹦狂,該 property 的值需要是列表中唯一的字符串或數(shù)字誓篱,且不能動(dòng)態(tài)改變。
② 保留關(guān)鍵字 *this 代表在 for 循環(huán)中的 item 本身凯楔,這種表示需要 item 本身是一個(gè)唯一的字符串或者數(shù)字窜骄。
Page({
data: {
objectArray: [
{id: 0, unique: 'unique_0'},
{id: 1, unique: 'unique_1'},
{id: 2, unique: 'unique_2'},
],
numberArray: [1, 2, 3, 4]
}
})
<switch wx:for="{{objectArray}}" wx:key="unique" style="display: block;"> {{item.id}} </switch>
<switch wx:for="{{numberArray}}" wx:key="*this" style="display: block;"> {{item}} </switch>
事件
上面簡(jiǎn)單提到過(guò) <view> 的事件綁定,通常在組件中綁定一個(gè)事件處理函數(shù)摆屯,在相應(yīng)的Page定義中寫上相應(yīng)的事件處理函數(shù)邻遏,參數(shù)是event。
事件分為冒泡事件(當(dāng)一個(gè)組件上的事件被觸發(fā)后,該事件會(huì)向父節(jié)點(diǎn)傳遞)和非冒泡事件(當(dāng)一個(gè)組件上的事件被觸發(fā)后准验,該事件不會(huì)向父節(jié)點(diǎn)傳遞)赎线。
冒泡事件有:touchstart(手指觸摸動(dòng)作開始)、touchmove(手指觸摸后移動(dòng))糊饱、touchcancel(手指觸摸動(dòng)作被打斷垂寥,如來(lái)電提醒,彈窗)济似、touchend(手指觸摸動(dòng)作結(jié)束)、tap(手指觸摸后馬上離開)盏缤、longtap(手指觸摸后砰蠢,超過(guò)350ms再離開)。
除此之外其他組件自定義事件如無(wú)特殊申明都是非冒泡事件唉铜,如<form/>的submit事件台舱,<input/>的input事件,<scroll-view/>的scroll事件潭流。
事件綁定的寫法同組件的屬性竞惋,以 key、value 的形式灰嫉。key 以 bind 或 catch 開頭事件的類型結(jié)尾拆宛,如bindtap, catchtouchstart,value 是一個(gè)字符串讼撒,需要在對(duì)應(yīng)的 Page 中定義同名的函數(shù)浑厚。
bind 事件綁定不會(huì)阻止冒泡事件向上冒泡,catch 事件綁定可以阻止冒泡事件向上冒泡根盒。
當(dāng)組件觸發(fā)事件時(shí)钳幅,邏輯層綁定該事件的處理函數(shù)會(huì)收到一個(gè)事件對(duì)象。
BaseEvent 基礎(chǔ)事件對(duì)象的屬性包括type(事件類型)炎滞、timeStamp(事件生成時(shí)的時(shí)間戳)敢艰、target(觸發(fā)事件的組件的一些屬性值集合)、currentTarget(當(dāng)前組件的一些屬性值集合)册赛。
CustomEvent 自定義事件對(duì)象繼承自BaseEvent钠导,并增加了 detail(額外的信息) 屬性。
TouchEvent 觸摸事件對(duì)象繼承自BaseEvent森瘪,并增加了 touches(觸摸事件辈双,當(dāng)前停留在屏幕中的觸摸點(diǎn)信息的數(shù)組)和 changedTouches(觸摸事件,當(dāng)前變化的觸摸點(diǎn)信息的數(shù)組) 屬性柜砾。
dataset 在組件中可以定義數(shù)據(jù)湃望,以data-開頭,多個(gè)單詞由連字符-連接,不能有大寫(大寫會(huì)自動(dòng)轉(zhuǎn)成小寫) 如data-element-type证芭,最終在 event.target.dataset 中會(huì)將連字符轉(zhuǎn)成駝峰e(cuò)lementType瞳浦。
登陸
小程序的登錄流程如下圖所示:
以下是我的登陸方法,放在 util.js 中废士,并傳入了一個(gè) function 的參數(shù) cb 作為登陸成功時(shí)的回調(diào)叫潦。如果不需要回調(diào)則不傳即可。通常會(huì)在程序啟動(dòng)時(shí)調(diào)用一次(可以在 app.js 的 onLaunch 中官硝,也可以在 index.js 的onLoad 中)矗蕊,之后在任何請(qǐng)求中碰到 access_token 過(guò)期或無(wú)效時(shí)再調(diào)用。
function login(cb) {
wx.login({
success: function (res) {
var code = res.code;
if (code) {
wx.getUserInfo({
success: function (res) {
var userInfo = res.userInfo;
wx.setStorageSync('user', userInfo);
wx.request({
url: 'https://server-host/login',
data: { 'code': code, 'user_info': userInfo },
method: 'POST',
success: function (res) {
if (res.data.data && res.data.data.access_token) {
wx.setStorageSync('accessToken', res.data.data.access_token);
typeof cb == 'function' && cb();
} else {
showFailedToast('服務(wù)器登陸失敗氢架,請(qǐng)退出后重新登錄');
}
},
fail: function (e) {
showFailedToast('服務(wù)器登陸失敗傻咖,請(qǐng)退出后重新登錄');
}
})
},
fail: function (e) {
showFailedToast('獲取用戶信息失敗,請(qǐng)退出后重新登錄');
}
})
} else {
showFailedToast('獲取微信登錄狀態(tài)失敗岖研,請(qǐng)退出后重新登錄');
}
},
fail: function (e) {
showFailedToast('微信登陸失敗卿操,請(qǐng)退出后重新登錄');
}
})
}
1、微信小程序需要調(diào)用 wx.login() 進(jìn)行登錄孙援,成功后會(huì)返回一個(gè) code害淤。
2、通過(guò) wx.getUserInfo() 獲取用戶信息拓售,雖然這個(gè) API 不需要用到 code窥摄,但也只能在 wx.login 成功后才能調(diào)用。獲取 userInfo 后础淤,可以通過(guò) wx.setStorageSync() 將用戶信息保存到本地緩存中溪王。
3、通過(guò) wx.request() 請(qǐng)求服務(wù)器的登錄api值骇,將 code 和 userInfo 傳過(guò)去莹菱,服務(wù)器會(huì)生成一個(gè)access_token(即上圖中的3rd_session,命名可以按照各自的習(xí)慣) 返回吱瘩,之后通過(guò) wx.setStorageSync() 將這個(gè) access_token 保存到本地緩存中道伟。
4、之后在任何地方進(jìn)行請(qǐng)求服務(wù)器的操作使碾,都可以通過(guò) wx.getStorageSync() 將本地保存的 access_token 取出來(lái)并作為參數(shù)帶上蜜徽。
5、由于 access_token 存在過(guò)期的問(wèn)題票摇,因此可以與服務(wù)器約定一個(gè)特殊的 sta拘鞋,比如 -500,作為 access_token 過(guò)期的標(biāo)識(shí)矢门,在任何請(qǐng)求中盆色,碰到返回的 sta 為 -500 時(shí)灰蛙,就可以重新調(diào)用 login 方法,傳入一個(gè)登陸成功后的回調(diào) function 作為參數(shù)隔躲,獲取新的 access_token 保存摩梧,并在登陸成功后的回調(diào) function 中再次發(fā)送請(qǐng)求。代碼如下:
function loadDetail(self) {
wx.request({
url: 'https://server-host/detail',
data: { 'vid': self.data.vid, 'access_token': wx.getStorageSync('accessToken') },
method: 'GET',
success: function (res) {
if (res.data.sta == -500) {
utils.login(function () {
loadDetail(self);
})
return;
}
...
},
fail: function () {
...
})
}
API
以上登陸過(guò)程中提到了幾個(gè)比較常用的微信API宣旱,如 wx.login()仅父、wx.getUserInfo()、wx.setStorageSync()浑吟、wx.getStorageSync()笙纤、wx.request() 等,另外還有很多的API组力,在 https://mp.weixin.qq.com/debug/wxadoc/dev/api/ 中寫的很清楚省容,這里就不一一說(shuō)明了,大家用的時(shí)候查看一下文檔即可忿项。