目錄結構
小程式包含一個描述整體程式的
app
和多個描述各自頁面的
page
。
描述頁面的四個檔案必須具有相同的路徑與檔案名
網頁開發者需要面對的環境是各式各樣的浏覽器,PC 端需要面對 IE、Chrome、QQ浏覽器等,在移動端需要面對Safari、Chrome以及 iOS、Android 系統中的各式 WebView 。而小程式開發過程中需要面對的是兩大作業系統 iOS 和 Android 的微信用戶端,以及用于輔助開發的小程式開發者工具,小程式中三大運作環境也是有所差別的,

JSON 配置
JSON 是一種資料格式,并不是程式設計語言,在小程式中,JSON扮演的靜态配置的角色。
我們可以看到在項目的根目錄有一個
app.json
和
project.config.json
,此外在
pages/logs
目錄下還有一個
logs.json
,我們依次來說明一下它們的用途。
app.json
是目前小程式的全局配置,包括了小程式的所有頁面路徑、界面表現、網絡逾時時間、底部 tab 等。QuickStart 項目裡邊的
app.json
配置内容如下:
工具配置 project.config.json(個性化配置)
通常大家在使用一個工具的時候,都會針對各自喜好做一些個性化配置,例如界面顔色、編譯配置等等,當你換了另外一台電腦重新安裝工具的時候,你還要重新配置。
考慮到這點,小程式開發者工具在每個項目的根目錄都會生成一個
project.config.json
,你在工具上做的任何配置都會寫入到這個檔案,當你重新安裝工具或者換電腦工作時,你隻要載入同一個項目的代碼包,開發者工具就自動會幫你恢複到當時你開發項目時的個性化配置,其中會包括編輯器的顔色、代碼上傳時自動壓縮等等一系列選項。
頁面配置 page.json
是以我們提供了
page.json
,讓開發者可以獨立定義每個頁面的一些屬性,例如剛剛說的頂部顔色、是否允許下拉重新整理等等
JSON 文法
這裡說一下小程式裡JSON配置的一些注意事項。
JSON檔案都是被包裹在一個大括号中 {},通過key-value的方式來表達資料。JSON的Key必須包裹在一個雙引号中,在實踐中,編寫 JSON 的時候,忘了給 Key 值加雙引号或者是把雙引号寫成單引号是常見錯誤。
JSON的值隻能是以下幾種資料格式,其他任何格式都會觸發報錯,例如 JavaScript 中的 undefined。
- 數字,包含浮點數和整數
- 字元串,需要包裹在雙引号中
- Bool值,true 或者 false
- 數組,需要包裹在方括号中 []
- 對象,需要包裹在大括号中 {}
- Null
還需要注意的是 JSON 檔案中無法使用注釋,試圖添加注釋将會引發報錯
要換一個思路了,在架構,其他的開發中,在html中的标簽,現在都叫
元件了,要記得轉換過來。所謂封裝好的元件,在使用的時候就已經有了
一些js,css互動的功能了。而标簽更多的還隻是語義化
标簽名字有點不一樣
往往寫 HTML 的時候,經常會用到的标簽是
div
,
p
,
span
,開發者在寫一個頁面的時候可以根據這些基礎的标簽組合出不一樣的元件,例如月曆、彈窗等等。換個思路,既然大家都需要這些元件,為什麼我們不能把這些常用的元件包裝起來,大大提高我們的開發效率。
從上邊的例子可以看到,小程式的
WXML
用的标簽是
view
,
button
,
text
等等,這些标簽就是小程式給開發者包裝好的基本能力,我們還提供了地圖、視訊、音頻等等元件能力。
多了一些
wx:if
這樣的屬性以及
這樣的表達式
在網頁的一般開發流程中,我們通常會通過
JS
操作
DOM
(對應
HTML
的描述産生的樹),以引起界面的一些變化響應使用者的行為。例如,使用者點選某個按鈕的時候,
JS
會記錄一些狀态到
JS
變量裡邊,同時通過
DOM
API 操控
DOM
的屬性或者行為,進而引起界面一些變化。當項目越來越大的時候,你的代碼會充斥着非常多的界面互動邏輯和程式的各種狀态變量,顯然這不是一個很好的開發模式,是以就有了 MVVM 的開發模式(例如 React, Vue),提倡把渲染和邏輯分離。簡單來說就是不要再讓
JS
直接操控
DOM
,
JS
隻需要管理狀态即可,然後再通過一種模闆文法來描述狀态和界面結構的關系即可。
小程式的架構也是用到了這個思路,如果你需要把一個
Hello World
的字元串顯示在界面上。
WXML 是這麼寫 :
<text>{{msg}}</text>
JS 隻需要管理狀态即可:
this.setData({ msg: "Hello World" })
通過 {{ }} 的文法把一個變量綁定到界面上,我們稱為資料綁定。僅僅通過資料綁定還不夠完整的描述狀态和界面的關系,還需要 if / else , for 等控制能力,
在小程式裡邊,這些控制能力都用 wx: 開頭的屬性來表達
WXSS 樣式
WXSS
具有
CSS
大部分的特性,小程式在
WXSS
也做了一些擴充和修改。
- 新增了尺寸機關。在寫
樣式時,開發者需要考慮到手機裝置的螢幕會有不同的寬度和裝置像素比,采用一些技巧來換算一些像素機關。CSS
在底層支援新的尺寸機關WXSS
,開發者可以免去換算的煩惱,隻要交給小程式底層來換算即可,由于換算采用的浮點數運算,是以運算結果會和預期結果有一點點偏差。rpx
- 提供了全局的樣式和局部樣式。和前邊
,app.json
的概念相同,你可以寫一個page.json
作為全局樣式,會作用于目前小程式的所有頁面,局部頁面樣式app.wxss
僅對目前頁面生效。page.wxss
- 此外
僅支援部分WXSS
選擇器CSS
JS 邏輯互動
我們改變資料必須用setData嗎? this.setDate({msg:hello world})
而且這個setData還必須在函數裡面
響應使用者的操作就是這麼簡單,更詳細的事件可以參考文檔 WXML - 事件 。
此外你還可以在 JS 中調用小程式提供的豐富的 API,利用這些 API 可以很友善的調起微信提供的能力,例如擷取使用者資訊、本地存儲、微信支付等。在前邊的 QuickStart 例子中,在
pages/index/index.js
就調用了 wx.getUserInfo 擷取微信使用者的頭像和昵稱,最後通過
setData
把擷取到的資訊顯示到界面上。更多 API 可以參考文檔 小程式的API 。
這些得自己看官方文檔的API内容
程式與頁面
微信用戶端在打開小程式之前,會把整個小程式的代碼包下載下傳到本地。
緊接着通過
app.json
的
pages
字段就可以知道你目前小程式的所有頁面路徑:
于是微信用戶端就把首頁的代碼裝載進來,通過小程式底層的一些機制,就可以渲染出這個首頁。
小程式啟動之後,在
app.js
定義的
App
執行個體的
onLaunch
回調會被執行:
整個小程式隻有一個 App 執行個體,是全部頁面共享的,更多的事件回調參考文檔 注冊程式 App 。
Page({
data: { // 參與頁面渲染的資料
logs: []
},
onLoad: function () {
// 頁面渲染後 執行
}
})
Page
是一個頁面構造器,這個構造器就生成了一個頁面。在生成頁面的時候,小程式架構會把
data
資料和
index.wxml
一起渲染出最終的結構,于是就得到了你看到的小程式的樣子。
在渲染完界面之後,頁面執行個體就會收到一個
onLoad
的回調,你可以在這個回調處理你的邏輯。
有關于
Page
構造器更多詳細的文檔參考 注冊頁面 Page
元件
小程式提供了豐富的基礎元件給開發者,開發者可以像搭積木一樣,組合各種元件拼合成自己的小程式。
<map bindmarkertap="markertap" longitude="廣州經度" latitude="廣州緯度"></map>
API
要擷取使用者的地理位置時,隻需要:
wx.getLocation({
type: \'wgs84\',
success: (res) => {
var latitude = res.latitude // 緯度
var longitude = res.longitude // 經度
}
})
調用微信掃一掃能力,隻需要:
wx.scanCode({
success: (res) => {
console.log(res)
}
})
需要注意的是:多數 API 的回調都是異步,你需要處理好代碼邏輯的異步問題。
2.1.2 JSON 文法
相比于XML ,JSON格式最大的優點是易于人的閱讀和編寫,通常不需要特殊的工具,就能讀懂和修改,是一種輕量級的資料交換格式。
JSON檔案都是被包裹在一個大括号中 {},通過key-value的方式來表達資料。
看起來同 JavaScript 的對象表達方式十分相似,但是有所不同。
報錯資訊的箭頭附近是有文法錯誤的
2.2 WXML 模闆
WXML 全稱是 WeiXin Markup Language,是小程式架構設計的一套标簽語言,結合小程式的基礎元件、事件系統,可以建構出頁面的結構。
使用 wx:elif 和 wx:else 來添加一個 else 塊:
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
因為 wx:if 是一個控制屬性,需要将它添加到一個标簽上。如果要一次性判斷多個元件标簽,可以使用一個 <block/> 标簽将多個元件包裝起來,并在上邊使用 wx:if 控制屬性。
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
2.2.5 清單渲染
在元件上使用 wx:for 控制屬性綁定一個數組,即可使用數組中各項的資料重複渲染該元件。預設數組的目前項的下标變量名預設為 index,數組目前項的變量名預設為 item
使用 wx:for-item 指定數組目前元素的變量名,使用 wx:for-index 指定數組目前下标的變量名:
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
就是說指定他們的變量名,不是叫index和item了
類似 block wx:if ,也可以将 wx:for 用在 <block/> 标簽上,以渲染一個包含多節點的結構塊。例如:
<block wx:for="{{[1, 2, 3]}}">
<view> {{index}}: </view>
<view> {{item}} </view>
</block>
難道不放在block裡就不能渲染多個标簽了嗎
2.2.6 模闆
WXML提供模闆(template),可以在模闆中定義代碼片段,然後在不同的地方調用。使用 name 屬性,作為模闆的名字。然後在
<template/>
内定義代碼片段,如:
這個東西我以前還百度過
看例子還是有點看不懂
但好像也少用 知道他是模闆就好了
就重複代碼段,到時候用到再百度
WXML 提供兩種檔案引用方式import和include。
import 可以在該檔案中使用目标檔案定義的 template,
需要注意的是 import 有作用域的概念,即隻會 import 目标檔案中定義的 template,而不會 import 目标檔案中 import 的 template,簡言之就是 import 不具有遞歸的特性。
例如:C 引用 B,B 引用A,在C中可以使用B定義的 template,在B中可以使用A定義的 template ,但是C不能使用A定義的template ,
2.3 WXSS 樣式
WXSS(WeiXin Style Sheets)是一套用于小程式的樣式語言,用于描述WXML的元件樣式,也就是視覺上的效果。
在小程式開發中,開發者不需要像Web開發那樣去優化樣式檔案的請求數量,隻需要考慮代碼的組織即可。樣式檔案最終會被編譯優化,具體的編譯原理我們留在後面章節再做介紹。
2.3.6 官方樣式庫
為了減輕開發者樣式開發的工作量,我們提供了WeUI.wxss基礎樣式庫。
WeUI是一套與微信原生視覺體驗一緻的基礎樣式庫,由微信官方設計團隊為微信内網頁和微信小程式量身設計,令使用者的使用感覺更加統一。包含button、cell、dialog、progress、toast、article、actionsheet、icon等各式原生。
2.4 JavaScript 腳本
小程式的主要開發語言是 JavaScript ,開發者使用 JavaScript 來開發業務邏輯以及調用小程式的 API 來完成業務需求。
小程式中的 JavaScript 是由ECMAScript 以及小程式架構和小程式 API 來實作的。同浏覽器中的JavaScript 相比沒有 BOM 以及 DOM 對象,是以類似 JQuery、Zepto這種浏覽器類庫是無法在小程式中運作起來的,同樣的缺少 Native 子產品和NPM包管理的機制,小程式中無法加載原生庫,也無法直接使用大部分的 NPM 包。
小程式目前可以運作在三大平台:
- iOS平台,包括iOS9、iOS10、iOS11
- Android平台
- 小程式IDE
小程式IDE提供文法轉碼工具幫助開發者,将 ECMAScript 6代碼轉為 ECMAScript 5代碼,進而在所有的環境都能得到很好的執行
開發者需要在項目設定中,勾選 ES6 轉 ES5 開啟此功能。
2.4.3 子產品化
浏覽器中,所有 JavaScript 是在運作在同一個作用域下的,定義的參數或者方法可以被後續加載的腳本通路或者改寫。同浏覽器不同,小程式中可以将任何一個JavaScript 檔案作為一個子產品,通過module.exports 或者 exports 對外暴露接口。(ES6提出的子產品化)
請看是一個簡單子產品示例,B.js 引用子產品A,并使用A暴露的multiplyBy2方法完成一個變量乘以 2 的操作。
// moduleA.js
module.exports = function( value ){
return value * 2;
}
// B.js
// 在B.js中引用子產品A
var multiplyBy2 = require(\'./moduleA\')
var result = multiplyBy2(4)
2.4.4 腳本的執行順序
浏覽器中,腳本嚴格按照加載的順序執行
而在小程式中的腳本執行順序有所不同。小程式的執行的入口檔案是 app.js 。并且會根據其中 require 的子產品順序決定檔案的運作順序,
當 app.js 執行結束後,小程式會按照開發者在 app.json 中定義的 pages 的順序,逐一執行。
就是說會先執行,App.js中的代碼
然後才會執行頁面中的js代碼
當需要使用全局變量的時,通過使用全局函數 getApp() 擷取全局的執行個體,并設定相關屬性值,來達到設定全局變量的目的
3.1 渲染層和邏輯層
從這個例子我們可以看到3個點:
1.渲染層和資料相關。
2.邏輯層負責産生、處理資料。
3.邏輯層通過 Page 執行個體的 setData 方法傳遞資料到渲染層。
1. 程式構造器App()
宿主環境提供了 App() 構造器用來注冊一個程式App,需要留意的是App() 構造器必須寫在項目根目錄的app.js裡,App執行個體是單例對象,在其他JS腳本中可以使用宿主環境提供的 getApp() 來擷取程式執行個體。
var appInstance = getApp()
其中onLaunch / onShow / onHide 三個回調是App執行個體的生命周期函數,我們會在後文展開;onError我們暫時不在本章展開,
這是應用的生命周期:頁面有頁面的生命周期
代碼清單3-4 App構造器
App({
onLaunch: function(options) {},
onShow: function(options) {},
onHide: function() {},
onError: function(msg) {},
globalData: \'I am global data\'
})
2. 程式的生命周期和打開場景
初次進入小程式的時候,微信用戶端初始化好宿主環境,同時從網絡下載下傳或者從本地緩存中拿到小程式的代碼包,把它注入到宿主環境,初始化完畢後,微信用戶端就會給App執行個體派發onLaunch事件,App構造器參數所定義的onLaunch方法會被調用。
進入小程式之後,使用者可以點選右上角的關閉,或者按手機裝置的Home鍵離開小程式,此時小程式并沒有被直接銷毀,我們把這種情況稱為“小程式進入背景狀态”,App構造器參數所定義的onHide方法會被調用。
當再次回到微信或者再次打開小程式時,微信用戶端會把“背景”的小程式喚醒,我們把這種情況稱為“小程式進入前台狀态”,App構造器參數所定義的onShow方法會被調用。
我們可以看到,App的生命周期是由微信用戶端根據使用者操作主動觸發的。為了避免程式上的混亂,我們不應該從其他代碼裡主動調用App執行個體的生命周期函數。
在微信用戶端中打開小程式有很多途徑:從群聊會話裡打開,從小程式清單中打開,通過微信掃一掃二維碼打開,從另外一個小程式打開目前小程式等,針對不同途徑的打開方式,小程式有時需要做不同的業務處理,是以微信用戶端會把打開方式帶給onLaunch和onShow的調用參數options,示例代碼以及詳細參數如代碼清單3-5和表3-2所示。需要留意小程式的宿主環境在疊代更新過程會增加不少打開場景,是以要擷取最新的場景值說明請檢視官方文檔:https://mp.weixin.qq.com/debug/wxadoc/dev/framework/app-service/app.html。
getApp()得到App.js裡面的執行個體
通過以上分析可以看出,setTimeout與setInterval的主要差別是:
setTimeout()方法隻運作一次,也就是說當達到設定的時間後就出發運作指定的代碼,運作完後就結束了,如果還想再次執行同樣的函數,可以在函數體内再次調用setTimeout(),可以達到循環調用的效果。
setInterval()是循環執行的,即每達到指定的時間間隔就執行相應的函數或者表達式,是真正的定時器。
與此同時,我們要特别留意一點,所有頁面的腳本邏輯都跑在同一個JsCore線程,頁面使用setTimeout或者setInterval的定時器,然後跳轉到其他頁面時,這些定時器并沒有被清除,需要開發者自己在頁面離開的時候進行清理。
2. 頁面構造器Page()
宿主環境提供了 Page() 構造器用來注冊一個小程式頁面,Page()在頁面腳本page.js中調用,Page() 的調用方式如代碼清單3-8所示。Page構造器接受一個Object參數,參數說明如表3-4所示,其中data屬性是目前頁面WXML模闆中可以用來做資料綁定的初始資料,我們會在後文展開讨論;onLoad / onReady / onShow / onHide /onUnload 5個回調是Page執行個體的生命周期函數,我們在後文展開;onPullDownRefresh / onReachBottom / onShareAppMessage / onPageScroll 4個回調是頁面的使用者行為,我們也會在後文展開。
代碼清單3-8 Page構造器
3. 頁面的生命周期和打開參數
頁面初次加載的時候,微信用戶端就會給Page執行個體派發onLoad事件,Page構造器參數所定義的onLoad方法會被調用,onLoad在頁面沒被銷毀之前隻會觸發1次,在onLoad的回調中,可以擷取目前頁面所調用的打開參數option,關于打開參數我們放在這一節的最後再展開闡述。
頁面顯示之後,Page構造器參數所定義的onShow方法會被調用,一般從别的頁面傳回到目前頁面時,目前頁的onShow方法都會被調用。
在頁面初次渲染完成時,Page構造器參數所定義的onReady方法會被調用,onReady在頁面沒被銷毀前隻會觸發1次,onReady觸發時,表示頁面已經準備妥當,在邏輯層就可以和視圖層進行互動了。
以上三個事件觸發的時機是onLoad早于 onShow,onShow早于onReady。
頁面不可見時,Page構造器參數所定義的onHide方法會被調用,這種情況會在使用wx.navigateTo切換到其他頁面、底部tab切換時觸發。
目前頁面使用wx.redirectTo或wx.navigateBack傳回到其他頁時,目前頁面會被微信用戶端銷毀回收,此時Page構造器參數所定義的onUnload方法會被調用。
在清單頁打開商品詳情頁時把商品的id傳遞過來,詳情頁通過剛剛說的onLoad回調的參數option就可以拿到商品id,進而繪制出對應的商品,
// pages/list/list.js
// 清單頁使用navigateTo跳轉到詳情頁
wx.navigateTo({ url: \'pages/detail/detail?id=1&other=abc\' })
// pages/detail/detail.js
Page({
onLoad: function(option) {
console.log(option.id)
console.log(option.other)
}
})
宿主環境所提供的Page執行個體的原型中有setData函數,我們可以在Page執行個體下的方法調用this.setData把資料傳遞給渲染層,進而達到更新界面的目的。
由于小程式的渲染層和邏輯層分别在兩個線程中運作,是以setData傳遞資料實際是一個異步的過程,
是以setData的第二個參數是一個callback回調,在這次setData對界面渲染完畢後觸發。
setData其一般調用格式是 setData(data, callback),其中data是由多個key: value構成的Object對象。
代碼清單3-11 使用setData更新渲染層資料
// page.js
Page({
onLoad: function(){
this.setData({
text: \'change data\'
}, function(){
// 在這次setData對界面渲染完畢後觸發
})
}
})
此外需要注意以下3點:
- 直接修改 Page執行個體的this.data 而不調用 this.setData 是無法改變頁面的狀态的,還會造成資料不一緻。
- 由于setData是需要兩個線程的一些通信消耗,為了提高性能,每次設定的資料不應超過1024kB。
- 不要把data中的任意一項的value設為undefined,否則可能會有引起一些不可預料的bug。
5. 頁面的使用者行為
小程式宿主環境提供了四個和頁面相關的使用者行為回調:
-
下拉重新整理 onPullDownRefresh
監聽使用者下拉重新整理事件,需要在app.json的window選項中或頁面配置page.json中設定enablePullDownRefresh為true。當處理完資料重新整理後,wx.stopPullDownRefresh可以停止目前頁面的下拉重新整理。
-
上拉觸底 onReachBottom
監聽使用者上拉觸底事件。可以在app.json的window選項中或頁面配置page.json中設定觸發距離onReachBottomDistance。在觸發距離内滑動期間,本事件隻會被觸發一次。
-
頁面滾動 onPageScroll
監聽使用者滑動頁面事件,參數為 Object,包含 scrollTop 字段,表示頁面在垂直方向已滾動的距離(機關px)。
-
使用者轉發 onShareAppMessage
隻有定義了此事件處理函數,右上角菜單才會顯示“轉發”按鈕,在使用者點選轉發按鈕的時候會調用,此事件需要return一個Object,包含title和path兩個字段,用于自定義轉發内容,如代碼清單3-13所示。
3.3 元件
需要注意,所有元件名和屬性都是小寫,多個單詞會以英文橫杠 "-" 進行連接配接。
對于一些容器元件,其内容可以聲明在其開始标簽和結束标簽之間。
這個mode屬性隻要image元件有嗎
mode(中文名模式)
我們介紹一下API一般調用的約定:
- wx.on* 開頭的 API 是監聽某個事件發生的API接口,接受一個 Callback 函數作為參數。當該事件觸發時,會調用 Callback 函數。
- 如未特殊約定,多數 API 接口為異步接口 ,都接受一個Object作為參數。
- API的Object參數一般由success、fail、complete三個回調來接收接口調用結果,示例代碼如代碼清單3-17所示,詳細說明如表3-9所示。
- wx.get* 開頭的API是擷取宿主環境資料的接口。
- wx.set* 開頭的API是寫入資料到宿主環境的接口。
事件是通過bindtap這個屬性綁定在元件上的,同時在目前頁面的Page構造器中定義對應的事件處理函數tapName,當使用者點選該view區域時,達到觸發條件生成事件tap,該事件處理函數tapName會被執行,同時還會收到一個事件對象event。