一、目錄結(jié)構(gòu)

1.1 主體(app)
由app.js、app.json、app.wxss組成,包括小程序的注冊、全局配置、頁面注冊、公共變量和方法,還有樣式。
1.1.1 注冊小程序
每個小程序都需要在 app.js 中調(diào)用 App() 方法注冊小程序?qū)嵗?,綁定生命周期回調(diào)函數(shù)、錯誤監(jiān)聽和頁面不存在監(jiān)聽函數(shù)等。整個小程序只有一個 App 實例,是全部頁面共享的。
App({
onLaunch: function () {},
onShow: function () {},
onHide: function () {},
globalData: {
appName: '',
tplId: {},
APPID: '',
domain: '',
userInfo: null,
userWXInfo: null
}
})
開發(fā)者可以通過 getApp() 方法獲取到全局唯一的 App 實例,獲取App上的數(shù)據(jù)或調(diào)用開發(fā)者注冊在 App 上的函數(shù)。
const appInstance = getApp()
console.log(appInstance.globalData.userInfo)
1.1.2 小程序全局配置
小程序根目錄下的 app.json 文件用來對微信小程序進(jìn)行全局配置,決定頁面文件的路徑、窗口表現(xiàn)、設(shè)置網(wǎng)絡(luò)超時時間、設(shè)置多 tab 等。
{
"pages": [
"pages/index/index",
"pages/logs/index"
],
"window": {
"navigationBarTitleText": "Demo"
},
"tabBar": {
"list": [{
"pagePath": "pages/index/index",
"text": "首頁"
}, {
"pagePath": "pages/logs/index",
"text": "日志"
}]
},
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
"debug": true,
"navigateToMiniProgramAppIdList": [
"wxe5f52902cf4de896"
]
}
1.2 頁面(Page)
一個小程序頁面由四個文件組成,分別是:index.js、index.wxml、index.json、index.wxss。
??為了方便開發(fā)者減少配置項,描述頁面的四個文件必須具有相同的路徑與文件名。
1.2.1 WXML
WXML(WeiXin Markup Language)是框架設(shè)計的一套標(biāo)簽語言,結(jié)合基礎(chǔ)組件、事件系統(tǒng),可以構(gòu)建出頁面的結(jié)構(gòu)。具有數(shù)據(jù)綁定、列表渲染、條件渲染、模板和引用等能力。
1.2.1.1 數(shù)據(jù)綁定
WXML 中的動態(tài)數(shù)據(jù)均來自對應(yīng) Page 的 data。語法使用大家熟悉的Mustache,比如:
<view>{{ message }}</view>
Page({
data: {
message: 'Hello,World'
}
})
更新數(shù)據(jù)需要用到Page原型鏈上的setData函數(shù)
this.setData({
message: 'updating view'
})
1.2.1.2 列表渲染
在組件上可以使用wx:for綁定一個數(shù)組,來循環(huán)渲染該組件。使用 wx:for-item 可以指定數(shù)組當(dāng)前元素的變量名,使用 wx:for-index 可以指定數(shù)組當(dāng)前下標(biāo)的變量名。比如:
<view wx:for="{{ array }}">
{{ index }}:{{ item.message }}
</view>
<view wx:for="{{ array }}" wx:for-index="idx" wx:for-item="itemName">
{{ idx }}:{{ itemName.message }}
</view>
Page({
data: {
array: [{
message: 'foo',
}, {
message: 'bar'
}]
}
})
如果列表中項目的位置會動態(tài)改變或者有新的項目添加到列表中,并且希望列表中的項目保持自己的特征和狀態(tài)(如 input 中的輸入內(nèi)容,switch 的選中狀態(tài)),需要使用 wx:key 來指定列表中項目的唯一的標(biāo)識符。
wx:key 的值以兩種形式提供:
字符串:代表在 for 循環(huán)的 array 中 item 的某個 property,該 property 的值需要是列表中唯一的字符串或數(shù)字,且不能動態(tài)改變。
保留關(guān)鍵字
*this代表在 for 循環(huán)中的 item 本身,這種表示需要 item 本身是一個唯一的字符串或者數(shù)字。
1.2.1.3 條件渲染
在框架中,使用 wx:if="" 來判斷是否需要渲染該代碼塊
<view wx:if="{{ length > 5 }}">1</view>
<view wx:elif="{{ length > 2 }}">2</view>
<view wx:else>3</view>
1.2.1.4 模板
WXML提供模板(template),可以在模板中定義代碼片段,然后在不同的地方調(diào)用。
<!--
index: int
msg: string
time: string
-->
<template name="msgItem">
<view>
<text>{{ index }}: {{ msg }}</text>
<text>Time: {{ time }}</text>
</view>
</template>
使用 is 屬性,聲明需要的使用的模板,然后將模板所需要的 data 傳入,如:
<template is="msgItem" data="{{ ...item }}"/>
Page({
data: {
item: {
index: 0,
msg: 'this is a template',
time: '2020-09-11'
}
}
})
注意:模板擁有自己的作用域,只能使用 data 傳入的數(shù)據(jù)以及模板定義文件中定義的 <wxs /> 模塊。
1.2.1.5 引用
WXML 提供兩種文件引用方式import和include。
import
import 有作用域的概念,即只會 import 目標(biāo)文件中定義的 template,而不會 import 目標(biāo)文件 import 的 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"/>
include
include 可以將目標(biāo)文件除了 <template/> <wxs/> 外的整個代碼引入,相當(dāng)于是拷貝到 include 位置,如:
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
<!-- header.wxml -->
<view> header </view>
<!-- footer.wxml -->
<view> footer </view>
1.2.2 JS
頁面文件夾下的.js文件里面是頁面的腳本代碼,通過調(diào)用Page方法注冊頁面,在方法內(nèi)可指定頁面的初始數(shù)據(jù)、生命周期函數(shù)、事件處理方法等。
?? 小程序腳本邏輯運行在JSCore中,JSCore沒有dom環(huán)境,因此小程序完全不支持dom操作
1.2.3 WXSS
頁面文件夾下的.wxss文件里面是頁面的樣式表
1.2.4 JSON
每一個小程序頁面也可以使用同名 .json 文件來對本頁面的窗口表現(xiàn)進(jìn)行配置,頁面中配置項會覆蓋 app.json 的 window 中相同的配置項。
二、生命周期
2.1 APP生命周期

首次(小程序沒有運行在后臺)打開小程序,觸發(fā)且只觸發(fā)一次onLaunch方法。
小程序初始化完成后,觸發(fā)onShow方法。
小程序切換到后臺,觸發(fā)onHidden方法。
小程序從后臺切換到前臺,觸發(fā)onShow方法。
小程序代碼出錯,觸發(fā)onError方法。
小程序要跳轉(zhuǎn)的頁面不存在,觸發(fā)onPageNotFound方法。
小程序進(jìn)入后臺一定時間,或者系統(tǒng)資源占用過高,會被銷毀。
2.2 頁面生命周期

打開Page頁面,頁面初始化,觸發(fā)onLoad方法。
頁面初始化完成,進(jìn)入前臺展示,觸發(fā)onShow方法。
首次渲染頁面完畢,觸發(fā)onReady方法,一個頁面只會觸發(fā)一次。
小程序切換到后臺或者跳轉(zhuǎn)到其他頁面時,觸發(fā)onHide方法。
小程序從后臺切換到前臺或者從其他頁面返回本頁面時,觸發(fā)onShow方法。
使用wx.redirectTo()或wx.navigateBack()等重定向方法銷毀頁面時,觸發(fā)onUnload方法。
2.3 app生命周期影響page生命周期

小程序初始化完成觸發(fā)App的onShow方法后,Page開始加載并只觸發(fā)一次Page的onLoad方法。
小程序切換到后臺時,先觸發(fā)Page的onHide方法再觸發(fā)App的onHide方法。
小程序從后臺進(jìn)入到前臺時,先執(zhí)觸發(fā)App的onShow方法再觸發(fā)Page的onShow方法
三、路由與通信
3.1 頁面路由
在小程序中所有頁面的路由全部由框架進(jìn)行管理。
開發(fā)者可以使用 getCurrentPages() 函數(shù)獲取當(dāng)前頁面棧。
| 路由方式 | 頁面棧表現(xiàn) | 跳轉(zhuǎn)方式 |
|---|---|---|
| 初始化 | 新頁面入棧 | 打開小程序 |
| 打開新頁面 | 新頁面入棧 | 使用wx.navigateTo方法或<navigator opten-type="navigateTo"/>組件 |
| 頁面重定向 | 當(dāng)前頁面出棧,新頁面入棧 | 使用wx.redirectTo方法或<navigator opten-type="redirectTo"/>組件 |
| 頁面返回 | 頁面不斷出棧,直到目標(biāo)返回頁 | 使用wx.navigateBack方法或<navigator opten-type="navigateBack"/>組件或點擊左上角返回按鈕 |
| Tab 切換 | 頁面全部出棧,只留下新的 Tab 頁面 | 使用wx.switchTab方法或<navigator opten-type="switchTab"/>組件或點擊tab按鈕 |
| 重加載 | 頁面全部出棧,只留下新的頁面 | 使用wx.reLaunch方法或<navigator opten-type="reLaunch"/>組件 |
wx.navigateTo():保留當(dāng)前頁面,跳轉(zhuǎn)到應(yīng)用內(nèi)除了tabbar頁面外的其他某個頁面。
wx.redirectTo():關(guān)閉當(dāng)前頁面,跳轉(zhuǎn)到應(yīng)用內(nèi)除了tabbar頁面外的其他某個頁面。
wx.navigateBack():關(guān)閉當(dāng)前頁面,返回上一頁面或多級頁面。
wx.switchTab():跳轉(zhuǎn)到tabBar頁面,并關(guān)閉其他所有非tabBar頁面。
wx.reLaunch():關(guān)閉所有頁面,打開到應(yīng)用內(nèi)的某個頁面。
頁面棧:
- wx.navigateTo()方法會增加頁面棧層數(shù),直到頁面棧為十層。
- wx.redirectTo()方法不會增加頁面棧層數(shù)。
- wx.navigateBack()方法會減少頁面棧層數(shù),直到頁面棧層數(shù)為一。
- wx.switchTab()和wx.reLaunch()方法會將頁面棧層數(shù)變?yōu)橐弧?/li>
- 可以在小程序頁面中通過getCurrentPages()方法獲取頁面棧,獲取到的第一個元素為首頁,最后一個元素為當(dāng)前頁。
- 小程序中頁面棧最多十層。
- 不要嘗試修改頁面棧,會導(dǎo)致路由以及頁面狀態(tài)錯誤。
- 不要在App.onLaunch的時候調(diào)用getCurrentPages(),此時 page 還沒有生成。
3.2 頁面通信
小程序頁面之間的通信有三種方式:全局變量、本地存儲、url傳參。
全局變量: globalData
本地存儲: 將數(shù)據(jù)存儲在本地緩存中指定的key中,除非用戶主動刪除或因存儲空間原因被系統(tǒng)清理,否則數(shù)據(jù)都一直可用,且不管是在app.js還是在其他page文件的js中都可對本地存儲進(jìn)行增刪改查。
url傳參: 在navigator組件的url參數(shù)內(nèi),需要跳轉(zhuǎn)的頁面路徑可用“?”后接參數(shù),參數(shù)鍵與參數(shù)值用“=”相連,不同參數(shù)用“&”分隔。其他組件可以用data-“參數(shù)名”格式綁定屬性,js內(nèi)可用event.currentTarget.dataset.“參數(shù)名”的格式獲取參數(shù)值,然后使用js跳轉(zhuǎn)方法,方法的ur參數(shù)內(nèi)跳轉(zhuǎn)路徑用“?”連接跳轉(zhuǎn)參數(shù)。
參數(shù)接收: 跳轉(zhuǎn)后的頁面在onLoad生命周期函數(shù)中可以接收一個參數(shù),該參數(shù)以json的形式接收url跳轉(zhuǎn)傳遞的值
Page({
onLoad: function(options) {
console.log(options)
}
})
四、常用功能
4.1 小程序登錄

- 調(diào)用 wx.login() 獲取 臨時登錄憑證code ,并回傳到開發(fā)者服務(wù)器。
- 調(diào)用 auth.code2Session 接口,換取 用戶唯一標(biāo)識 OpenID 和 會話密鑰 session_key。
4.2 UnionID 機制說明
如果開發(fā)者擁有多個移動應(yīng)用、網(wǎng)站應(yīng)用、和公眾帳號(包括小程序),可通過 UnionID 來區(qū)分用戶的唯一性,因為只要是同一個微信開放平臺帳號下的移動應(yīng)用、網(wǎng)站應(yīng)用和公眾帳號(包括小程序),用戶的 UnionID 是唯一的。換句話說,同一用戶,對同一個微信開放平臺下的不同應(yīng)用,UnionID是相同的。
4.3 獲取手機號
獲取微信用戶綁定的手機號,需先調(diào)用wx.login接口。
因為需要用戶主動觸發(fā)才能發(fā)起獲取手機號接口,所以該功能不由 API 來調(diào)用,需用 button 組件的點擊來觸發(fā)。
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber"></button>
Page({
getPhoneNumber (e) {
console.log(e.detail.errMsg)
console.log(e.detail.iv)
console.log(e.detail.encryptedData)
}
})
4.4 分享轉(zhuǎn)發(fā)
通過給 button 組件設(shè)置屬性 open-type="share",可以在用戶點擊按鈕后觸發(fā) Page.onShareAppMessage 事件:
<button open-type="share"></button>
4.5 授權(quán)
獲取用戶授權(quán): 開發(fā)者可以使用wx.getSetting獲取用戶當(dāng)前的授權(quán)狀態(tài)。
打開設(shè)置頁面: 開發(fā)者可以使用wx.openSetting打開設(shè)置界面,引導(dǎo)用戶開啟授權(quán)。
提前發(fā)起授權(quán)請求: 開發(fā)者可以使用wx.authorize在調(diào)用需授權(quán) API 之前,提前向用戶發(fā)起授權(quán)請求。
常用授權(quán)接口:
| scope | 對應(yīng)接口 | 描述 |
|---|---|---|
| scope.userInfo | wx.getUserInfo | 用戶信息 |
| scope.userLocation | wx.getLocation, wx.chooseLocation | 地理位置 |
| scope.userLocationBackground | wx.startLocationUpdateBackground | 后臺定位 |
| scope.address | wx.chooseAddress | 通訊地址 |
| scope.invoiceTitle | wx.chooseInvoiceTitle | 發(fā)票抬頭 |
| scope.invoice | wx.chooseInvoice | 獲取發(fā)票 |
| scope.werun | wx.getWeRunData | 微信運動步數(shù) |
| scope.record | wx.startRecord | 錄音功能 |
| scope.writePhotosAlbum | wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum | 保存到相冊 |
| scope.camera | camera組件 | 攝像頭 |
- 如果用戶未接受或拒絕過此權(quán)限,會彈窗詢問用戶,用戶點擊同意后方可調(diào)用接口;
- 如果用戶已授權(quán),可以直接調(diào)用接口;
- 一旦用戶明確同意或拒絕過授權(quán),其授權(quán)關(guān)系會記錄在后臺,直到用戶主動刪除小程序。
- 如果用戶已拒絕授權(quán),則不會出現(xiàn)彈窗,而是直接進(jìn)入接口 fail 回調(diào)。請開發(fā)者兼容用戶拒絕授權(quán)的場景。
?? 注意:
-
wx.authorize({scope: "scope.userInfo"}),不會彈出授權(quán)窗口,請使用<button open-type="getUserInfo" /> - 需要授權(quán)
scope.userLocation、scope.userLocationBackground時必須配置地理位置用途說明。
4.6 獲取系統(tǒng)信息
小程序可以使用wx.getSystemInfo()方法獲取系統(tǒng)信息。
wx.getSystemInfo({
success (res) {
console.log(res.model)
console.log(res.pixelRatio)
console.log(res.windowWidth)
console.log(res.windowHeight)
console.log(res.language)
console.log(res.version)
console.log(res.platform)
}
})
wx.getSystemInfoSync()為wx.getSystemInfo()的同步版本。
4.7 獲取圖片
小程序可以使用wx.chooseImage()方法獲取圖片,來源可以選擇系統(tǒng)相冊或相機拍照。
wx.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success (res) {
// tempFilePath可以作為img標(biāo)簽的src屬性顯示圖片
const tempFilePaths = res.tempFilePaths
}
})
wx.chooseImage()方法獲取到的文件路徑為本地臨時路徑,需要將文件長期保存需要調(diào)用wx.saveFile()方法。
wx.chooseImage({
success: function(res) {
const tempFilePaths = res.tempFilePaths
wx.saveFile({
tempFilePath: tempFilePaths[0],
success (res) {
const savedFilePath = res.savedFilePath
}
})
}
})
4.8 掃碼
小程序使用wx.scanCode()方法可以調(diào)起客戶端掃碼界面進(jìn)行掃碼
// 允許從相機和相冊掃碼
wx.scanCode({
success (res) {
console.log(res)
}
})
// 只允許從相機掃碼
wx.scanCode({
onlyFromCamera: true,
success (res) {
console.log(res)
}
})
五、其他開發(fā)事項
5.1 原生組件
小程序中的部分組件是由客戶端創(chuàng)建的原生組件,這些組件有:
cameracanvas-
input(僅在focus時表現(xiàn)為原生組件) live-playerlive-pushermaptextareavideo
由于原生組件脫離在 WebView 渲染流程外,因此在使用時有以下限制:
- 原生組件的層級是最高的,所以頁面中的其他組件無論設(shè)置 z-index 為多少,都無法蓋在原生組件上。后插入的原生組件可以覆蓋之前的原生組件。
- 原生組件無法在picker-view組件中使用?;A(chǔ)庫 2.4.4 以下版本,原生組件不支持在 scroll-view、swiper、movable-view 中使用。
- 部分CSS樣式無法應(yīng)用于原生組件,例如:無法對原生組件設(shè)置CSS動畫;無法定義原生組件為 position:fixed;不能在父級節(jié)點使用overflow:hidden來裁剪原生組件的顯示區(qū)域。
- 原生組件的事件監(jiān)聽不能使用bind:eventname的寫法,只支持bindeventname。原生組件也不支持catch和capture的事件綁定方式。
- 原生組件會遮擋vConsole彈出的調(diào)試面板。在工具上,原生組件是用web組件模擬的,因此很多情況并不能很好的還原真機的表現(xiàn),建議在使用到原生組件時盡量在真機上進(jìn)行調(diào)試。
為了解決原生組件層級最高的限制。小程序?qū)iT提供了 cover-view 和 cover-image 組件,可以覆蓋在部分原生組件上面。這兩個組件也是原生組件,但是使用限制與其他原生組件有所不同。
可覆蓋的原生組件包括 map、video、canvas、camera、live-player、live-pusher
只支持嵌套 cover-view、cover-image、button。組件屬性的長度單位默認(rèn)為px,2.4.0起支持傳入單位(rpx/px)。
5.2 分包
某些情況下,開發(fā)者需要將小程序劃分成不同的子包,在構(gòu)建時打包成不同的分包,用戶在使用時按需進(jìn)行加載。
在構(gòu)建小程序分包項目時,構(gòu)建會輸出一個或多個分包。每個使用分包小程序必定含有一個主包。所謂的主包,即放置默認(rèn)啟動頁面/TabBar 頁面,以及一些所有分包都需用到公共資源/JS 腳本;而分包則是根據(jù)開發(fā)者的配置進(jìn)行劃分。
在小程序啟動時,默認(rèn)會下載主包并啟動主包內(nèi)頁面,當(dāng)用戶進(jìn)入分包內(nèi)某個頁面時,客戶端會把對應(yīng)分包下載下來,下載完成后再進(jìn)行展示。
目前小程序分包大小有以下限制:
- 整個小程序所有分包大小不超過 16M
- 單個分包/主包大小不能超過 2M
對小程序進(jìn)行分包,可以優(yōu)化小程序首次啟動的下載時間,以及在多團(tuán)隊共同開發(fā)時可以更好的解耦協(xié)作。
開發(fā)者通過在 app.json subpackages 字段聲明項目分包結(jié)構(gòu):
{
"pages":[
"pages/index",
"pages/logs"
],
"subpackages": [
{
"root": "packageA",
"pages": [
"pages/cat",
"pages/dog"
]
}, {
"root": "packageB",
"name": "pack2",
"pages": [
"pages/apple",
"pages/banana"
]
}
]
}