第2章
小程式基礎知識
第1章為小程式入門的各項事宜,接下來本章将介紹微信小程式開發的基礎知識:項目有哪些配置檔案,微信小程式的各種配置,WXSS樣式語言,邏輯層.js腳本,WXML視圖層開發等。
2.1 項目配置檔案
可以在項目根目錄使用project.config.json檔案(參見1.3.1節)對項目進行配置,項目配置檔案的内容參見表2-1。
表2-1 項目配置檔案

其中,compileType的有效值如下。
- miniprogram:目前為普通小程式項目。
- plugin:目前為小程式插件項目。
setting中可以指定的内容如下:
scripts中指定自定義預處理的指令如下。
- beforeCompile:編譯前預處理指令。
- beforePreview:預覽前預處理指令。
- beforeUpload:上傳前預處理指令。
packOptions用于配置項目在打包過程中的選項。打包是預覽、上傳時對項目進行的必須步驟。目前可以指定packOptions.ignore字段,忽略配置打包時符合指定規則的檔案或檔案夾,以跳過打包過程,這些檔案或檔案夾将不會出現在預覽或上傳的結果内。
packOptions.ignore為一對象數組,對象元素類型如下:
其中,type可以取值為folder、file、suffix、prefix、regexp、glob,分别對應檔案夾、檔案、字尾、字首、正規表達式、Glob規則。所有規則值都會自動忽略大小寫。
value字段的值若表示檔案或檔案夾路徑,以小程式目錄(miniprogramRoot)為根目錄。regexp、glob僅1.02.1809260及以上版本工具支援。
配置示例代碼如下:
{
"packOptions": {
"ignore": [{
"type": "file",
"value": "test/test.js"
}, {
"type": "folder",
"value": "test"
}, {
"type": "suffix",
"value": ".webp"
}, {
"type": "prefix",
"value": "test-"
}, {
"type": "glob",
"value": "test/**/*.js"
}, {
"type": "regexp",
"value": "\\.jsx$"
}]
}
}
這部分設定的更改可能需要重新打開項目才能生效。
debugOptions用于配置在對項目代碼進行調試時的選項。目前可以指定debugOptions.hidedInDevtools字段,用于配置是否顯示調試器的源代碼。
hidedInDevtools的配置規則和packOptions.ignore是一緻的。當某個.js檔案符合此規則時,調試器Sources面闆中此檔案源代碼正文内容将被隐藏,顯示代碼示例如下:
// xxx.js has been hided by project.config.json
注:配置此規則後,可能需要關閉并重新打開項目才能看到效果。
項目配置代碼示例如下:
{
"miniprogramRoot": "./src",
"qcloudRoot": "./svr",
"setting": {
"postcss": true,
"es6": true,
"minified": true,
"urlCheck": false
},
"packOptions": {
"ignore": []
},
"debugOptions": {}
}
2.2 全局配置和頁面配置
每個微信小程式項目都有一個全局配置檔案和多個頁面配置檔案。全局配置檔案針對整個微信小程式項目的相關配置資訊;頁面配置檔案隻針對對應的頁面,每個微信小程式都有一個對應的頁面配置檔案。全局配置檔案和頁面配置檔案如果有相同的配置項目,頁面配置檔案的優先級高于全局配置檔案,也就是以頁面配置檔案的效果為主。
2.2.1 全局配置
我們利用小程式根目錄下的app.json檔案對微信小程式進行全局配置,決定頁面檔案的路徑、視窗表現、設定網絡逾時時間、設定多tab等。
每個微信小程式項目隻有一個全局配置檔案。下面是一個包含了部分常用配置選項的app.json:
{
"pages": [
"pages/index/index",
"pages/logs/index"
],
"window": {
"navigationBarTitleText": "Demo"
},
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首頁"
}, {
"pagePath": "pages/logs/logs",
"text": "日志"
}]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true,
"navigateToMiniProgramAppIdList": [
"wxe5f52902cf4de896"
]
}
app.json配置項清單參見表2-2。
表2-2 app.json配置項清單
(1)pages
pages用于指定小程式由哪些頁面組成,是一個數組,數組中每一項都對應一個頁面的“路徑+檔案名”資訊。檔案名不需要寫檔案字尾,開發架構會自動去尋找對應位置的.json、.js、.wxml、.wxss四個檔案進行處理。數組的第一項代表小程式的初始頁面(首頁)。小程式中新增/減少頁面,都需要對pages數組進行修改。例如,如下開發目錄中:
├── app.js
├── app.json
├── app.wxss
├── pages
│ │── index
│ │ ├── index.wxml
│ │ ├── index.js
│ │ ├── index.json
│ │ └── index.wxss
│ └── logs
│ ├── logs.wxml
│ └── logs.js
└── utils
要在app.json中編寫頁面,則需要配置pages數組,代碼如下所示。
{
"pages":[
"pages/index/index",
"pages/logs/logs"
]
}
(2)window
window用于設定小程式的狀态欄、導覽列、标題、視窗背景色等,屬性參見表2-3。
表2-3 window屬性
其中,HexColor為十六進制顔色值,#ffffff表示白色,#000000表示黑色。
navigationStyle隻在app.json中生效。開啟custom後,低版本用戶端需要做好相容。開發者工具基礎庫版本切到用戶端6.7.2版本開始,navigationStyle: custom對元件無效。
app.json示例代碼如下,界面示例如圖2-1所示:
{
"window":{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "微信接口功能示範",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
}
(3)tabBar
tabBar用于設定小程式多tab。例如,用戶端視窗的底部或頂部有tab欄可以切換頁面,可以通過tabBar配置項指定tab欄的表現,以及tab切換時顯示的對應頁面。tabBar屬性參見表2-4。
表2-4 tabBar屬性
其中,list接受一個數組,隻能配置最少2個、最多5個tab。tab按數組的順序排序,每個項都是一個對象,其屬性值如下:
當屬性iconPath和selectedIconPath的postion為top時,不顯示icon。list屬性如圖2-2所示。
圖2-2 list屬性示例
(4)networkTimeout
networkTimeout用于指定各類網絡請求的逾時時間,機關均為毫秒,networkTimeout屬性參見表2-5。
表2-5 networkTimeout屬性
(5)debug
可以在開發者工具中開啟debug模式,在開發者工具的控制台面闆,調試資訊以info的形式給出,其資訊有Page的注冊、頁面路由、資料更新、事件觸發等,可以幫助開發者快速定位一些常見的問題。
(6)functionalPages
基礎庫2.1.0開始支援,低版本需做相容處理。啟用插件功能頁時,插件所有者小程式需要将functionalPages設定為true。
(7)subpackages
微信用戶端6.6.0、基礎庫1.7.3及以上版本支援。啟用分包加載時,聲明項目分包結構。寫成subPackages也支援。
(8)workers
使用Worker處理多線程任務時,設定Worker代碼放置的目錄。
(9)requiredBackgroundModes
微信用戶端6.7.2及以上版本支援。聲明需要背景運作的能力,類型為數組。目前支援以下項目:Audio背景音樂播放,代碼示例如下:
{
"pages": ["pages/index/index"],
"requiredBackgroundModes": ["audio"]
}
此處聲明了背景運作的接口,開發版和體驗版上可以直接生效,正式版還需通過稽核。
(10)plugins
基礎庫1.9.6開始支援,低版本需做相容處理。聲明小程式需要使用的插件。
(11)preloadRule
基礎庫2.3.0開始支援,低版本需做相容處理。聲明分包預下載下傳的規則。
(12)resizable
基礎庫2.3.0開始支援,低版本需做相容處理。在iPad上運作的小程式可以設定支援螢幕旋轉。
(13)navigateToMiniProgramAppIdList
基礎庫2.4.0開始支援,低版本需做相容處理。當小程式需要使用wx.navigateToMini-Program接口跳轉到其他小程式時,需要先在配置檔案中聲明需要跳轉的小程式的AppID清單,最多允許填寫10個。
2.2.2 頁面配置
每一個小程式的頁面可以使用.json檔案對本頁面的視窗表現進行配置。頁面配置隻能設定app.json中部分window配置項的内容,頁面中配置項會覆寫app.json的window中相同的配置項,頁面配置屬性參見表2-6。
配置樣例代碼如下:
{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "微信接口功能示範",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light"
}
表2-6 頁面配置屬性
頁面的.json隻能設定window相關的配置項,以決定本頁面的視窗表現,是以無須寫window這個鍵。
2.3 WXSS樣式語言
WXSS(WeiXin Style Sheets)是一套樣式語言,用于描述WXML的元件樣式。WXSS用來決定WXML的元件應該怎麼顯示。為了适應廣大的前端開發者,WXSS具有CSS大部分特性。同時,為了更适合開發微信小程式,WXSS對CSS進行了修改和擴充。
與CSS相比,在微信小程式WXSS擴充的特性有:
- 尺寸機關。
- 樣式導入。
- 全局樣式和局部樣式。
内聯樣式和選擇器沿用了CSS的功能寫法。
1.尺寸機關
尺寸機關為rpx(responsive pixel),可以根據螢幕寬度進行自适應。規定螢幕寬為750rpx。如在iPhone6上,螢幕寬度為375px,共有750個實體像素,則750rpx=375px= 750實體像素,1rpx=0.5px=1實體像素,不同裝置的換算方式如下:
在開發微信小程式時,建議設計師使用iPhone6作為視覺稿的标準。
在較小的螢幕上不可避免會有一些毛刺,在開發時盡量避免這種情況。
2.樣式導入
可以使用@import語句導入外聯樣式表,@import後跟需要導入的外聯樣式表的相對路徑,用分号“;”表示語句結束。代碼示例如下:
/** common.wxss **/
.small-p {
padding:5px;
}
/** app.wxss **/
@import "common.wxss";
.middle-p {
padding:15px;
}
3.全局樣式與局部樣式
定義在app.wxss中的樣式為全局樣式,作用于每一個頁面。在page的.wxss檔案中定義的樣式為局部樣式,隻作用于對應的頁面,并會覆寫app.wxss中相同的選擇器。
4.内聯樣式
架構元件上支援使用style和class屬性來控制元件的樣式,說明如下。
- style:靜态的樣式統一寫到class中,style接收動态的樣式,在運作時會進行解析,盡量不要将靜态的樣式寫進style,以免影響渲染速度,代碼示例如下:
<view style="color:{{color}};" />
- class:用于指定樣式規則,其屬性值是樣式規則中類選擇器名(樣式類名)的集合,樣式類名不需要帶上.,樣式類名之間用空格分隔,代碼示例如下:
<view class="normal_view" />
5.選擇器
目前支援的選擇器有:
還有很多支援的選擇器,不在這裡一一列出,讀者可以自行嘗試,我們在後面第3章會講解常用選擇器在微信小程式中的使用。
2.4 邏輯層.js腳本
小程式開發架構的邏輯層使用JavaScript引擎,向小程式開發者提供JavaScript代碼的運作環境以及微信小程式的特有功能。邏輯層對資料進行處理并發送給視圖層,同時接受視圖層的事件回報。開發者寫的所有代碼最終将打包成一份JavaScript檔案,并在小程式啟動的時候運作,直到小程式銷毀。這一行為類似于ServiceWorker,是以邏輯層也稱為App Service。
在JavaScript的基礎上,為了友善小程式開發增加了以下功能:
- 增加App和Page方法,進行程式和頁面的注冊。
- 增加getApp和getCurrentPages方法,用來擷取App執行個體和目前頁面棧。
- 提供豐富的API,如微信使用者資料、掃一掃、支付等微信特有能力。
- 每個頁面有獨立的作用域,并提供子產品化能力。
小程式架構的邏輯層并非運作在浏覽器中,是以JavaScript在Web中的一些能力無法使用,如window、document等。
2.4.1 App方法
小程式的App方法包含一系列函數,例如:App(Object)、onLaunch(Object)、on-Show(Object)、onHide()、onError(String error)、onPageNotFound(Object)、getApp(Object)等。
1. App(Object)
App()函數用來注冊一個小程式。接受一個Object參數,指定小程式的生命周期回調等。App()必須在app.js中調用且隻能調用一次,否則會出現無法預期的後果。Object參數說明參見表2-7。
表2-7 App()函數的Object參數表
前台、背景的含義是,當使用者點選左上角關閉,或者按了裝置Home鍵離開微信,小程式并沒有直接銷毀,而是進入了背景;當再次進入微信或再次打開小程式,小程式又會從背景進入前台。需要注意的是,隻有當小程式進入背景一定時間,或者系統資源占用過高,小程式才會被真正銷毀。
關閉小程式(基礎庫版本1.1.0開始支援)是指,當使用者從掃一掃、轉發等入口(場景值為1007, 1008, 1011, 1025)進入小程式,且沒有置頂小程式的情況下退出,小程式會被銷毀。
小程式運作機制在基礎庫版本1.4.0有所改變:上面“關閉小程式”邏輯在新版本已不适用。
代碼示例如下:
App({
onLaunch: function(options) {
// Do something initial when launch.
},
onShow: function(options) {
// Do something when show.
},
onHide: function() {
// Do something when hide.
},
onError: function(msg) {
console.log(msg)
},
globalData: 'I am global data'
})
2. onLaunch(Object)
onLaunch()函數在小程式初始化完成時觸發,全局隻觸發一次,其中,Object參數說明參見表2-8。
表2-8 onLaunch()函數的Object參數表
其中,referrerInfo.appId場景值參見表2-9。
表2-9 referrerInfo.appId場景值
3. onShow(Object)
小程式啟動時或從背景進入前台顯示時觸發onShow()函數。Object參數說明與on-Launch()函數一緻。
4. onHide()
小程式從前台進入背景時觸發onHide()函數。
5. onError(String error)
小程式發生腳本錯誤或者API調用失敗時觸發onError()函數。參數說明如下:
6. onPageNotFound(Object)
基礎庫1.9.90開始支援,低版本需做相容處理。小程式要打開的頁面不存在時觸發onPageNotFound()函數。Object參數說明如下:
開發者可以在onPageNotFound回調中進行重定向,但必須在回調中同步處理,異步處理(例如setTimeout異步執行)無效。代碼示例如下:
App({
onPageNotFound(res) {
wx.redirectTo({
url: 'pages/...'
}) //如果是tabBar頁面,請使用wx.switchTab
}
})
- 如果開發者沒有添加onPageNotFound監聽,當跳轉頁面不存在時,将推入“微信用戶端原生的頁面不存在”提示頁面。
- 如果onPageNotFound回調中又重定向到另一個不存在的頁面,将推入“微信用戶端原生的頁面不存在”提示頁面,并且不再回調onPageNotFound。
7. getApp(Object)
getApp()函數是全局函數,可以用來擷取小程式App執行個體。Object參數說明如下:
// other.js
var appInstance = getApp()
console.log(appInstance.globalData) // I am global data
不要在定義于App()内的函數中調用getApp(),使用this就可以擷取App執行個體。
通過getApp()擷取執行個體之後,不要私自調用生命周期函數。
2.4.2 運作機制
小程式啟動會有兩種情況,一種是“冷啟動”,一種是“熱啟動”。假如使用者已經打開過某小程式,然後在一定時間内再次打開該小程式,此時無須重新啟動,隻需将背景狀态的小程式切換到前台,這個過程就是“熱啟動”。“冷啟動”指的是使用者首次打開小程式,或小程式被微信主動銷毀後再次打開,此時小程式需要重新加載啟動。
小程式冷啟動時如果發現有新版本,将會異步下載下傳新版本的代碼包,并同時用用戶端本地的包進行啟動,即新版本的小程式需要等下一次冷啟動才會應用。如果需要馬上應用最新版本,可以使用wx.getUpdateManager API進行處理。
小程式沒有重新開機的概念。當小程式進入背景,用戶端會維持一段時間的運作狀态,超過一定時間後(目前是5分鐘)會被微信主動銷毀。當短時間内(5分鐘)連續收到兩次以上系統記憶體告警,會對小程式進行銷毀。
再次打開邏輯:基礎庫1.4.0開始支援,低版本需做相容處理。
打開小程式有A和B兩類場景。
A場景,打開首頁。場景值有以下幾項:
B場景,打開小程式指定的某個頁面。場景值為除上面場景以外的其他内容,再次打開一個小程式的邏輯如下:
2.4.3 場景值
目前支援的場景值參數見表2-10。基礎庫1.1.0開始支援,低版本需做相容處理。
表2-10 場景值參數
可以在App的onLaunch和onShow中擷取上述場景值,部分場景值下還可以擷取來源應用、公衆号或小程式的AppID。
由于Android系統限制,目前還無法擷取到按Home鍵退出到桌面,然後從桌面再次進小程式的場景值,對于這種情況,會保留上一次的場景值。
2.4.4 Page方法
小程式的Page方法用于頁面的注冊和配置。
1.頁面Page()函數
Page()函數用來注冊一個頁面。接受一個Object類型參數,指定頁面的初始資料、生命周期回調、事件處理函數等。Object參數說明參見表2-11。
表2-11 Page()函數的Object參數
//index.js
Page({
data: {
text: "This is page data."
},
onLoad: function(options) {
// Do some initialize when page load.
},
onReady: function() {
// Do something when page ready.
},
onShow: function() {
// Do something when page show.
},
onHide: function() {
// Do something when page hide.
},
onUnload: function() {
// Do something when page close.
},
onPullDownRefresh: function() {
// Do something when pull down.
},
onReachBottom: function() {
// Do something when page reach bottom.
},
onShareAppMessage: function () {
// return custom share data when user share.
},
onPageScroll: function() {
// Do something when page scroll
},
onResize: function() {
// Do something when page resize
},
onTabItemTap(item) {
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
},
// Event handler.
viewTap: function() {
this.setData({
text: 'Set some data for updating view.'
}, function() {
// this is setData callback
})
},
customData: {
hi: 'MINA'
}
})
頁面可以像自定義元件一樣使用Component來建立,這樣就可以使用自定義元件的特性。
2.初始資料data
data是頁面第一次渲染使用的初始資料。頁面加載時,data将會以JSON字元串的形式由邏輯層傳至渲染層,是以data中的資料必須可以轉成JSON的類型,如字元串、數字、布爾值、對象、數組等。
渲染層可以通過WXML對資料進行綁定。代碼示例如下:
<view>{{text}}</view>
<view>{{array[0].msg}}</view>
Page({
data: {
text: 'init data',
array: [{msg: '1'}, {msg: '2'}]
}
})
3.生命周期回調函數
注冊頁面時的生命周期回調函數包括onLoad()、onShow()、onReady()、onHide()、on-Unload()。
onLoad(Object query)頁面加載時觸發。一個頁面隻會調用一次,可以在onLoad的參數中擷取打開目前頁面路徑中的參數。
參數說明如下:
onShow()頁面顯示/切入前台時觸發。
onReady()頁面初次渲染完成時觸發。一個頁面隻會調用一次,該函數執行完畢代表頁面已經準備妥當,可以和視圖層進行互動。
對界面内容進行設定的API如wx.setNavigationBarTitle,請在onReady之後進行。
onHide()頁面隐藏/切入背景時觸發,如navigateTo或底部tab切換到其他頁面,小程式切入背景等。
onUnload()頁面解除安裝時觸發,如redirectTo或navigateBack到其他頁面時。
4.頁面事件處理函數
(1)onPullDownRefresh()
監聽使用者下拉重新整理事件。需要在app.json的window選項中或頁面配置中開啟enable-PullDownRefresh。可以通過wx.startPullDownRefresh觸發下拉重新整理,調用後觸發下拉重新整理動畫,效果與使用者手動下拉重新整理一緻。當處理完資料重新整理後,wx.stopPullDownRefresh可以停止目前頁面的下拉重新整理。
(2)onReachBottom()
監聽使用者上拉觸底事件。可以在app.json的window選項中或頁面配置中設定觸發距離onReachBottomDistance。在觸發距離内滑動,本事件隻會被觸發一次。
(3)onPageScroll(Object)
監聽使用者滑動頁面事件。Object參數如下:
(4)onShareAppMessage(Object)
監聽使用者點選頁面内轉發按鈕
(<button>元件open-type="share")
或右上角菜單“轉發”按鈕的行為,并自定義轉發内容。
隻有定義了此事件處理函數,右上角菜單才會顯示“轉發”按鈕。
Object參數如下:
此事件需要傳回一個Object,用于自定義轉發内容,傳回内容如下:
圖檔路徑可以是本地檔案路徑、代碼封包件路徑或者網絡圖檔路徑。支援PNG及JPG。顯示圖檔長寬比是5∶4。
Page({
onShareAppMessage: function (res) {
if (res.from === 'button') {
//來自頁面内轉發按鈕
console.log(res.target)
}
return {
title: '自定義轉發标題',
path: '/page/user?id=123'
}
}
})
(5)onTabItemTap(Object)
基礎庫1.9.0開始支援,低版本需做相容處理。點選tab時觸發,Object參數如下:
Page({
onTabItemTap(item) {
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
}
})
5.元件事件處理函數
Page中還可以定義元件事件處理函數。在渲染層的元件中加入事件綁定,當事件被觸發時,執行Page中定義的事件處理函數。
<view bindtap="viewTap"> click me </view>
Page({
viewTap: function() {
console.log('view tap')
}
})
6. route
Page.route表示到目前頁面的路徑,類型為String,基礎庫1.2.0開始支援,低版本需做相容處理。代碼示例如下:
Page({
onShow: function() {
console.log(this.route)
}
})
7. setData
Page.prototype.setData(Object data, Function callback)函數用于将資料從邏輯層發送到視圖層(異步),同時改變對應的this.data的值(同步)。參數說明如下:
Object以key: value的形式表示,将this.data中key對應的值改變成value。
其中key可以用資料路徑的形式給出,支援改變數組中的某一項或對象的某個屬性,如array[2].message,a.b.c.d,并且不需要在this.data中預先定義。
- 直接修改this.data而不調用this.setData是無法改變頁面的狀态的,并會造成資料不一緻。
- 僅支援設定可JSON化的資料。單次設定的資料不能超過1024kb,請盡量避免一次設定過多的資料。請不要把data中任何一項的value設為undefined,否則這一項将不被設定并可能遺留一些潛在問題。
<!--index.wxml-->
<view>{{text}}</view>
<button bindtap="changeText"> Change normal data </button>
<view>{{num}}</view>
<button bindtap="changeNum"> Change normal num </button>
<view>{{array[0].text}}</view>
<button bindtap="changeItemInArray"> Change Array data </button>
<view>{{object.text}}</view>
<button bindtap="changeItemInObject"> Change Object data </button>
<view>{{newField.text}}</view>
<button bindtap="addNewField"> Add new data </button>
// index.js
Page({
data: {
text: 'init data',
num: 0,
array: [{text: 'init data'}],
object: {
text: 'init data'
}
},
changeText: function() {
// this.data.text = 'changed data' //不要直接修改this.data
//應該使用setData
this.setData({
text: 'changed data'
})
},
changeNum: function() {
//或者,可以修改this.data之後馬上用setData設定一下修改了的字段
this.data.num = 1
this.setData({
num: this.data.num
})
},
changeItemInArray: function() {
//對于對象或數組字段,可以直接修改一個其下的子字段,這樣做通常比修改整個對象或數組更好
this.setData({
'array[0].text':'changed data'
})
},
changeItemInObject: function(){
this.setData({
'object.text': 'changed data'
});
},
addNewField: function() {
this.setData({
'newField.text': 'new data'
})
}
})
8.生命周期
了解生命周期的含義,将會幫助你了解開發。圖2-3為Page執行個體的生命周期。
圖2-3 Page執行個體的生命周期
2.4.5 路由
在小程式中,所有頁面的路由全部由架構進行管理。架構以棧的形式維護了目前的所有頁面。當發生路由切換的時候,頁面棧的表現如表2-12所示。
getCurrentPages()函數用于擷取目前頁面棧的執行個體,以數組形式按棧的順序給出,第一個元素為首頁,最後一個元素為目前頁面。
不要嘗試修改頁面棧,會導緻路由以及頁面狀态錯誤。
不要在App.onLaunch的時候調用getCurrentPages(),此時page還沒有生成。
路由的觸發方式以及頁面生命周期函數參見表2-13。
表2-13 路由的觸發方式以及頁面生命周期函數
例如,A、B頁面為TabBar頁面,C是從A頁面打開的頁面,D頁面是從C頁面打開的頁面,Tab切換對應的生命周期如下:
- navigateTo或redirectTo隻能打開非tabBar頁面。
- switchTab隻能打開tabBar頁面。
- reLaunch可以打開任意頁面。
- 頁面底部的tabBar由頁面決定,即隻要是定義為tabBar的頁面,底部都有tabBar。
- 調用頁面路由所帶的參數可以在目标頁面的onLoad中擷取。
2.4.6 子產品化
在JavaScript檔案中聲明的變量和函數隻在該檔案中有效;不同的檔案中可以聲明相同名字的變量和函數,不會互相影響。通過全局函數getApp()可以擷取全局的應用執行個體,如果需要全局的資料可以在App()中設定,代碼示例如下:
// app.js
App({
globalData: 1
})
// a.js
// The localValue can only be used in file a.js.
var localValue = 'a'
// Get the app instance.
var app = getApp()
// Get the global data and change it.
app.globalData++
// b.js
// You can redefine localValue in file b.js, without interference with the local-
Value in a.js.
var localValue = 'b'
// If a.js it run before b.js, now the globalData shoule be 2.
console.log(getApp().globalData)
可以将一些公共的代碼抽離成一個單獨的.js檔案,作為一個子產品。子產品隻有通過module.exports或exports才能對外暴露接口。
exports是module.exports的一個引用,在子產品裡面随意更改exports的指向會造成未知的錯誤。是以推薦開發者采用module.exports來暴露子產品接口,除非你已經清晰知道這兩者的關系。小程式目前不支援直接引入node_modules,開發者需要使用到node_modules時建議拷貝相關的代碼到小程式的目錄中,或者使用小程式支援的npm功能。
// common.js
function sayHello(name) {
console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
console.log(`Goodbye ${name} !`)
}
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
在需要使用這些子產品的檔案中,使用require(path)将公共代碼引入,代碼示例如下:
var common = require('common.js')
Page({
helloMINA: function() {
common.sayHello('MINA')
},
goodbyeMINA: function() {
common.sayGoodbye('MINA')
}
})
require暫時不支援絕對路徑。
2.4.7 API
小程式開發架構提供豐富的微信原生API,可以友善地調用微信提供的能力,如擷取使用者資訊、本地存儲、支付功能等。通常,小程式API有以下幾種類型:事件監聽API,同步API,異步API。
1.事件監聽API
以on開頭的API為事件監聽API,用來監聽某個事件是否觸發,如wx.onSocketOpen、wx.onCompassChange等。這類API接受一個回調函數作為參數,當事件觸發時會調用這個回調函數,并将相關資料以參數形式傳入。代碼示例如下:
wx.onCompassChange(function (res) {
console.log(res.direction)
})
2.同步API
以Sync結尾的API都是同步API,如wx.setStorageSync、wx.getSystemInfoSync等。此外,也有一些其他的同步API,如wx.createWorker、wx.getBackgroundAudioManager等。同步API的執行結果可以通過函數傳回值直接擷取,如果執行出錯會抛出異常。代碼示例如下:
try {
wx.setStorageSync('key', 'value')
} catch (e) {
console.error(e)
}
3.異步API
大多數API都是異步的,如wx.request、wx.login等。這類API接口通常都接受一個Object類型的參數,這個參數用于指定如何接收接口調用結果。
異步API的Object參數說明如下:
API通常的回調函數有三個:success、fail和complete。回調函數調用時會傳入一個Object類型參數,包含以下字段:
異步API的執行結果需要通過Object類型的參數中傳入的對應回調函數擷取。部分異步API也會有傳回值,可以用來實作更豐富的功能,如wx.request、wx.connectSockets等。代碼示例如下:
wx.login({
success(res) {
console.log(res.code)
}
})
2.5 WXML視圖層開發
WXML(WeiXin Markup Language)是架構設計的類似于HTML的标簽語言,結合基礎元件、事件系統就可以建構出頁面的結構,組成.wxml檔案。WXML中的動态資料均來自對應Page的data。本節主要講解視圖層開發中.wxml檔案常用的文法,包含資料綁定、清單渲染、條件渲染、模闆、事件、引用等處理。
2.5.1 資料綁定
資料綁定是指在小程式的.js檔案裡,将data定義的各類資料顯示在.wxml頁面中。當然data裡面定義的各類資料可以通過其他方式進行變更。
1.簡單綁定
資料簡單綁定是指使用Mustache文法(雙大括号)将變量包起來。
.wxml檔案代碼示例如下:
<view> {{ message }} </view>
.js檔案代碼示例如下:
Page({
data: {
message: 'Hello MINA!'
}
})
元件屬性需要在雙引号之内,.wxml檔案代碼示例如下:
<view id="item-{{id}}"> </view>
.js檔案代碼示例如下:
Page({
data: {
id: 0
}
})
控制屬性需要在雙引号之内,.wxml檔案代碼示例如下:
<view wx:if="{{condition}}"> </view>
.js檔案代碼示例如下:
Page({
data: {
condition: true
}
})
關鍵字(需要在雙引号之内)包括。
- true:boolean類型的true,代表真值。
- false:boolean類型的false,代表假值。
<checkbox checked="{{false}}"> </checkbox>
不要直接寫checked="false",其計算結果是一個字元串,轉成boolean類型後代表真值。
2.運算
可以在{{}}内進行簡單的運算,支援如下幾種運算:
三元運算,.wxml檔案代碼示例如下:
<view hidden="{{flag ? true : false}}"> Hidden </view>
算數運算,.wxml檔案代碼示例如下:
<view> {{a + b}} + {{c}} + d </view>
Page({
data: {
a: 1,
b: 2,
c: 3
}
})
view中的内容為3+3+d。
邏輯判斷,.wxml檔案代碼示例如下:
<view wx:if="{{length > 5}}"> </view>
字元串運算,.wxml檔案代碼示例如下:
<view>{{"hello" + name}}</view>
Page({
data:{
name: 'MINA'
}
})
資料路徑運算,.wxml檔案代碼示例如下:
<view>{{object.key}} {{array[0]}}</view>
Page({
data: {
object: {
key: 'Hello '
},
array: ['MINA']
}
})
3.組合
也可以在Mustache内直接進行組合,構成新的數組或者對象。
(1)數組
<view wx:for="{{[zero, 1, 2, 3, 4]}}"> {{item}} </view>
Page({
data: {
zero: 0
}
})
最終組合成數組[0, 1, 2, 3, 4]。
(2)對象
<template is="objectCombine" data="{{for: a, bar: b}}"></template>
Page({
data: {
a: 1,
b: 2
}
})
最終組合成對象{for: 1, bar: 2}。
也可以用擴充運算符...将一個對象展開。.wxml檔案代碼示例如下:
<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}"></template>
Page({
data: {
obj1: {
a: 1,
b: 2
},
obj2: {
c: 3,
d: 4
}
}
})
最終組合成對象{a: 1, b: 2, c: 3, d: 4, e: 5}。
如果對象的key和value相同,也可以間接地表達。.wxml檔案代碼示例如下:
<template is="objectCombine" data="{{foo, bar}}"></template>
Page({
data: {
foo: 'my-foo',
bar: 'my-bar'
}
})
最終組合成對象{foo: 'my-foo', bar:'my-bar'}。
上述方式可以随意組合,但如存在變量名相同的情況,後邊的會覆寫前面的,如:
<template is="objectCombine" data="{{...obj1, ...obj2, a, c: 6}}"></template>
Page({
data: {
obj1: {
a: 1,
b: 2
},
obj2: {
b: 3,
c: 4
},
a: 5
}
})
最終組合成的對象是{a: 5, b: 3, c: 6}。花括号和引号之間如果有空格,将最終被解析成字元串。
<view wx:for="{{[1,2,3]}} ">
{{item}}
</view>
等同于代碼:
<view wx:for="{{[1,2,3] + ' '}}">
{{item}}
</view>
2.5.2 清單渲染
清單渲染是指,在小程式.js檔案裡,把data定義的數組資料通過for循環語句顯示在.wxml頁面中。當然data裡面定義的各類資料可以通過其他方式進行變更。在實際的應用中,通過清單渲染可以輸出産品清單、新聞清單等。
1. wx:for
在元件上使用wx:for控制屬性綁定一個數組,即可使用數組中各項的資料重複渲染該元件。
數組的目前項的下标變量名預設為index,數組目前項的變量名預設為item,.wxml檔案代碼示例如下:
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
.js檔案代碼示例如下:
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
使用wx:for-item可以指定數組目前元素的變量名,使用wx:for-index可以指定數組目前下标的變量名,.wxml檔案代碼示例如下:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
wx:for也可以嵌套,下面是一個九九乘法表的.wxml檔案代碼示例:
<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>
2. block wx:for
可以将wx:for用在标簽上,以渲染一個包含多節點的結構塊。.wxml檔案代碼示例如下:
<block wx:for="{{[1, 2, 3]}}">
<view> {{index}}: </view>
<view> {{item}} </view>
</block>
并不是一個元件,它僅僅是一個包裝元素,不會在頁面中做任何渲染,隻接受控制屬性。
3. wx:key
如果清單中項目的位置會動态改變或者有新的項目添加到清單中,并且希望清單中的項目保持自己的特征和狀态(如中的輸入内容,的選中狀态),需要使用wx:key來指定清單中項的唯一辨別符。
wx:key的值有以下兩種形式。
- 字元串:代表在for循環的array中item的某個property,該property的值需要是清單中唯一的字元串或數字,且不能動态改變。
- 保留關鍵字(*this):代表在for循環中的item本身,這種表示需要item本身是一個唯一的字元串或者數字,例如,當資料改變觸發渲染層重新渲染的時候,會校正帶有key的元件,架構會将它們重新排序,而不是重新建立,以確定元件保持自身的狀态,并且提高清單渲染時的效率。如不提供wx:key,會報錯;如果明确知道該清單是靜态的,或者不必關注其順序,可以選擇忽略。
<switch wx:for="{{objectArray}}" wx:key="unique" style="display: block;"> {{item.id}} </switch>
<button bindtap="switch"> Switch </button>
<button bindtap="addToFront"> Add to the front </button>
<switch wx:for="{{numberArray}}" wx:key="*this" style="display: block;"> {{item}} </switch>
<button bindtap="addNumberToFront"> Add to the front </button>
Page({
data: {
objectArray: [
{id: 5, unique: 'unique_5'},
{id: 4, unique: 'unique_4'},
{id: 3, unique: 'unique_3'},
{id: 2, unique: 'unique_2'},
{id: 1, unique: 'unique_1'},
{id: 0, unique: 'unique_0'},
],
numberArray: [1, 2, 3, 4]
},
switch: function(e) {
const length = this.data.objectArray.length
for (let i = 0; i < length; ++i) {
const x = Math.floor(Math.random() * length)
const y = Math.floor(Math.random() * length)
const temp = this.data.objectArray[x]
this.data.objectArray[x] = this.data.objectArray[y]
this.data.objectArray[y] = temp
}
this.setData({
objectArray: this.data.objectArray
})
},
addToFront: function(e) {
const length = this.data.objectArray.length
this.data.objectArray = [{id: length, unique: 'unique_' + length}].concat (this.data.objectArray)
this.setData({
objectArray: this.data.objectArray
})
},
addNumberToFront: function(e){
this.data.numberArray = [ this.data.numberArray.length + 1 ].concat(this.data.numberArray)
this.setData({
numberArray: this.data.numberArray
})
}
})
注意,當wx:for的值為字元串時,會将字元串解析成字元串數組,.wxml檔案代碼示例如下:
<view wx:for="array">
{{item}}
</view>
等同于:
<view wx:for="{{['a','r','r','a','y']}}">
{{item}}
</view>
花括号和引号之間如果有空格,将最終被解析成字元串,.wxml檔案代碼示例如下:
<view wx:for="{{[1,2,3]}} ">
{{item}}
</view>
<view wx:for="{{[1,2,3] + ' '}}" >
{{item}}
</view>
2.5.3 條件渲染
條件渲染是指,根據if語句中的條件來決定if語句所在區塊是顯示還是隐藏,如果if條件語句為True,則渲染顯示;如果if條件語句為False,則隐藏不做渲染顯示。在實際開發中,可以通過按鈕來改變if條件語句的變量狀态(True和False之間轉換)來實作某個區塊的顯示或隐藏。
1. wx:if
在架構中,使用wx:if="{{condition}}"來判斷是否需要渲染該代碼塊,.wxml檔案代碼示例如下:
<view wx:if="{{condition}}"> True </view>
也可以用wx:elif和wx:else來添加一個else塊,.wxml檔案代碼示例如下:
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
2. block wx:if
因為wx:if是一個控制屬性,需要将它添加到一個标簽上。如果要一次性判斷多個元件标簽,可以使用一個标簽将多個元件包裝起來,并在上邊使用wx:if控制屬性,.wxml檔案代碼示例如下:
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
3. wx:if vs hidden
因為wx:if之中的模闆也可能包含資料綁定,是以當wx:if的條件值切換時,架構有一個局部渲染的過程,以確定條件塊在切換時銷毀或重新渲染。
同時,wx:if也是惰性的,如果初始渲染條件為False,架構什麼也不做,在條件第一次變成真的時候才開始局部渲染。相比之下,hidden就簡單多了,元件始終會被渲染,隻是簡單地控制顯示與隐藏。一般來說,wx:if有更高的切換消耗,而hidden有更高的初始渲染消耗。是以,在需要頻繁切換的情景下,用hidden更好;而在運作時條件不大可能改變時,則用wx:if較好。
2.5.4 模闆
模闆用于将一些公用的WXML代碼單獨整理成一個.wxml檔案,然後在有需要的地方直接引入即可。可以在模闆中定義代碼片段,然後在不同的地方調用。
1.定義模闆
使用name屬性作為模闆的名字,然後在
<template/>
内定義代碼片段。.wxml檔案代碼示例如下:
<!--
index: int
msg: string
time: string
-->
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
2.使用模闆
使用is屬性聲明需要使用的模闆,然後将模闆所需要的data傳入。.wxml檔案代碼示例如下:
<template is="msgItem" data="{{...item}}"/>
.js檔案代碼示例如下:
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2016-09-15'
}
}
})
is屬性可以使用Mustache文法,動态決定具體需要渲染哪個模闆。.wxml檔案代碼示例如下:
<template name="odd">
<view> odd </view>
</template>
<template name="even">
<view> even </view>
</template>
<block wx:for="{{[1, 2, 3, 4, 5]}}">
<template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>
模闆擁有自己的作用域,隻能使用data傳入的資料以及模闆定義檔案中定義的子產品。
2.5.5 事件
事件是控件可以識别的操作,如按下某個按鈕,選擇某個複選框。每一種控件有自己可以識别的事件,如小程式頁面的加載、按鈕的單擊、表單的送出等事件,也包括編輯框(文本框)的文本改變事件等。
事件有如下特征:
- 事件是視圖層到邏輯層的通信方式。
- 事件可以将使用者的行為回報到邏輯層進行處理。
- 事件可以綁定在元件上,當達到觸發事件,就會執行邏輯層中對應的事件處理函數。
- 事件對象可以攜帶額外資訊,如id、dataset、touches。
1.事件的使用方式
在元件中綁定一個事件處理函數,如bindtap,當使用者點選該元件的時候,會在該頁面對應的Page中找到相應的事件處理函數,.wxml檔案代碼示例如下:
<view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view>
在相應的Page定義中寫入相應的事件處理函數,參數是event,.js檔案代碼示例如下:
Page({
tapName: function(event) {
console.log(event)
}
})
可以看到,log出來的資訊大緻如下:
{
"type":"tap",
"timeStamp":895,
"target": {
"id": "tapTest",
"dataset": {
"hi":"WeChat"
}
},
"currentTarget": {
"id": "tapTest",
"dataset": {
"hi":"WeChat"
}
},
"detail": {
"x":53,
"y":14
},
"touches":[{
"identifier":0,
"pageX":53,
"pageY":14,
"clientX":53,
"clientY":14
}],
"changedTouches":[{
"identifier":0,
"pageX":53,
"pageY":14,
"clientX":53,
"clientY":14
}]
}
2.事件分類
事件分為冒泡事件和非冒泡事件。
- 冒泡事件:當一個元件上的事件被觸發後,該事件會向父節點傳遞。
- 非冒泡事件:當一個元件上的事件被觸發後,該事件不會向父節點傳遞。
WXML的冒泡事件參見表2-14。
表2-14 WXML的冒泡事件清單
除上表之外的其他元件自定義事件如無特殊聲明都是非冒泡事件,如
的submit事件,的input事件,的scroll事件。
3.事件綁定和冒泡
事件綁定的寫法與元件的屬性相關,分為key和value兩種形式:
- key以bind或catch開頭,後跟事件的類型,如bindtap、catchtouchstart。自基礎庫版本1.5.0起,在非原生元件中,bind和catch後可以緊跟一個冒号,其含義不變,如bind:tap、catch:touchstart。
- value是一個字元串,需要在對應的Page中定義同名的函數,否則當觸發事件的時候會報錯。
bind事件綁定不會阻止冒泡事件向上冒泡,catch事件綁定可以阻止冒泡事件向上冒泡。
例如,在下面這個例子中,點選inner view會依次調用handleTap3和handleTap2(因為tap事件會冒泡到middle view,而middle view阻止了tap事件冒泡,不再向父節點傳遞),點選middle view會觸發handleTap2,點選outer view會觸發handleTap1,.wxml檔案代碼示例如下:
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
</view>
</view>
</view>
4.事件的捕獲階段
自基礎庫版本1.5.0起,觸摸類事件支援捕獲階段。捕獲階段位于冒泡階段之前,在捕獲階段中,事件到達節點的順序與冒泡階段恰好相反。需要在捕獲階段監聽事件時,可以采用capture-bind、capture-catch關鍵字,後者将中斷捕獲階段和取消冒泡階段。
在下面的.wxml檔案代碼中,點選inner view會依次調用handleTap2、handleTap4、handleTap3、handleTap1:
<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
如果将上面代碼中的第一個capture-bind改為capture-catch,将隻觸發handleTap2。
<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
5.事件對象
如無特殊說明,當元件觸發事件時,邏輯層綁定該事件的處理函數會收到一個事件對象。
BaseEvent基礎事件對象屬性如下:
其中,type代表事件的類型。
timeStamp頁面打開到觸發事件所經過的毫秒數。
target觸發事件的源元件,屬性如下:
currentTarget事件綁定的目前元件,屬性如下:
說明:target和currentTarget可以參考上例,點選inner view時,handleTap3收到的事件對象target和currentTarget都是inner,而handleTap2收到的事件對象target就是inner,currentTarget就是middle。
CustomEvent自定義事件對象屬性(繼承BaseEvent)如下:
TouchEvent觸摸事件對象屬性(繼承BaseEvent)如下:
6. dataset
在元件中可以定義資料,這些資料将會通過事件傳遞給SERVICE。書寫方式:以data-開頭,多個單詞由連字元-連結,不能有大寫(大寫會自動轉成小寫),如data-element-type,最終在event.currentTarget.dataset中會将連字元轉成駝峰形式,如elementType。
<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>
Page({
bindViewTap:function(event){
event.currentTarget.dataset.alphaBeta === 1 // -會轉為駝峰寫法
event.currentTarget.dataset.alphabeta === 2 //大寫會轉為小寫
}
})
7. touches
touches是一個數組,每個元素為一個Touch對象(canvas觸摸事件中攜帶的touches是CanvasTouch數組)。表示目前停留在螢幕上的觸摸點。
Touch對象屬性如下:
8. changedTouches
changedTouches資料格式同touches,表示有變化的觸摸點,如從無變有(touchstart),位置變化(touchmove),從有變無(touchend、touchcancel)。
9. detail
自定義事件所攜帶的資料,如表單元件的送出事件會攜帶使用者的輸入,媒體的錯誤事件會攜帶錯誤資訊,詳見元件定義中各個事件的定義。
點選事件的detail帶有的x和y,同pageX和pageY,代表到文檔左上角的距離。
2.5.6 引用
WXML提供兩種檔案引用方式:import和include。
1. import
import可以在該檔案中使用目标檔案定義的template,例如,在item.wxml中定義了一個名為item的template,.wxml檔案代碼示例如下:
<!-- item.wxml -->
<template name="item">
<text>{{text}}</text>
</template>
在index.wxml中引用了item.wxml,就可以使用item模闆,.wxml檔案代碼示例如下:
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
import有作用域的概念,即隻輸入目标檔案中定義的模闆,而不輸入目标檔案自己輸入的模闆。例如,C import B,B import A,在C中可以使用B定義的template,在B中可以使用A定義的template,但是C不能使用A定義的template。
<!-- A.wxml -->
<template name="A">
<text> A template </text>
</template>
<!-- B.wxml -->
<import src="a.wxml"/>
<template name="B">
<text> B template </text>
</template>
<!-- C.wxml -->
<import src="b.wxml"/>
<template is="A"/> <!-- Error! Can not use tempalte when not import A. -->
<template is="B"/>
2. include
include可以将目标檔案除
<template/> <wxs/>
之外的整個代碼引入,相當于拷貝到include位置,.wxml檔案代碼示例如下:
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
<!-- header.wxml -->
<view> header </view>
<!-- footer.wxml -->
<view> footer </view>