網(wǎng)上已經(jīng)有很多前端開發(fā)者分享了小程序的入坑心得, 本篇文章不刻意重復(fù)造輪子, 面向有過Vue.js開發(fā)經(jīng)驗(yàn)正準(zhǔn)備接觸微信小程序開發(fā)的讀者.
從框架入手
早些版本的原生小程序基礎(chǔ)庫存在組件化開發(fā)困難和npm模塊無法使用等問題, 騰訊團(tuán)隊(duì)開源了wepy解決了老版原生小程序開發(fā)的短板. 后來支付寶,今日頭條等app相繼推出自己的小程序, 于是mpvue跨小程序端框架出現(xiàn); 再到后來出現(xiàn)了多端開發(fā)框架uni-app, taro等.
使用框架開發(fā)小程序的開發(fā)者可以按自己的需求和習(xí)慣選擇框架:
- Vue系開發(fā)者, 不需要跨端:
wepy - Vue系需跨端的開發(fā)者:
uni-app - React系需跨端的開發(fā)者:
taro
如果你是在糾結(jié)選擇哪個(gè)框架, 筆者推薦uni-app, 有幾點(diǎn)理由:
- 比
wepy更貼近Vue開發(fā)風(fēng)格 - 跨端相對(duì)靠譜,這里有一篇同類框架評(píng)測(cè). (
uni-app官方做的,但比較客觀用心) - 文檔完善, 社區(qū)和企鵝群相當(dāng)活躍
- 插件市場(chǎng)資源較豐富
主要注意的是, 無論使用哪個(gè)框架(特別是多端開發(fā)框架), 踩坑次數(shù)都不會(huì)少; 另外小程序基礎(chǔ)庫也在不斷完善, 目前也支持了組件化開發(fā)和npm模塊, 喜歡清爽簡(jiǎn)潔方式開發(fā)的不妨用原生小程序進(jìn)行開發(fā).
從原生小程序入手
所謂原生小程序開發(fā), 就是不使用上述對(duì)小程序基礎(chǔ)庫和語法二次封裝和構(gòu)建的框架進(jìn)行小程序開發(fā)的方式.
為了寫清楚小程序的語法和能力及其生態(tài)規(guī)范, 說實(shí)話官方文檔已經(jīng)做得不錯(cuò)了, 但同時(shí)學(xué)習(xí)成本也不低. 閱讀此文檔時(shí), 推薦開發(fā)者先快速通讀一兩遍除服務(wù)端相關(guān)模塊(服務(wù)端,云開發(fā))外的部分,并閱讀以下筆者針對(duì)vue開發(fā)者給一些"捷徑", 快速熟悉小程序開發(fā).然后再到編碼時(shí)邊寫變查, 逐步熟悉.
項(xiàng)目搭建
新建項(xiàng)目
你需要脫離vue-cli等腳手架工具, 下載微信小程序的官方IDE,然后新建一個(gè)小程序項(xiàng)目,其中AppID是在微信公眾平臺(tái)創(chuàng)建小程序應(yīng)用時(shí)獲取,如果沒有客店點(diǎn)擊使用測(cè)試號(hào),足夠開發(fā)階段使用(正式appid才可以正式發(fā)布小程序)。

新建成功后就有有了一個(gè)可運(yùn)行的demo,這里介紹一下最簡(jiǎn)單的小程序需要哪些目錄和文件:
pages- 存放頁面的目錄,每個(gè)頁面自成一個(gè)文件夾,需包含同名的js,wxss,json,wxml文件, 下一小節(jié)詳細(xì)介紹
utils- 非必需目錄,存放工具方法,公共方法等
app.js- 入口文件,類似vue.js應(yīng)用中實(shí)例化Vue對(duì)象的js(一般命名為app.js或者main.js)
app.wxss- 公共樣式表(wxss你可以看做css語法子集),這里所寫樣式都是全局的,你無需在任何文件引入
project.config.json- IDE的配置目錄,無需手動(dòng)配置
sitemap.json- 微信搜索索引配置,配置你的某些頁面是否可以在當(dāng)前小程序內(nèi)被搜索,有興趣了解的同學(xué)可參考文檔

當(dāng)然,實(shí)際項(xiàng)目中目錄會(huì)稍微復(fù)雜,以筆者某個(gè)小程序項(xiàng)目為例:

有些大家已經(jīng)熟悉的或者已介紹文件不介紹:
components- 存放小程序自定義組件目錄
resource- 存放圖片、字體等靜態(tài)文件的目錄,習(xí)慣上還常命名static
config.js- 常量配置文件,當(dāng)然你可以放在其他你喜歡存放的目錄
miniprogram_npm- npm依賴被微信開發(fā)者工具二次構(gòu)建生成的目錄,不需手動(dòng)創(chuàng)建,后面會(huì)有npm依賴安裝相關(guān)的介紹
UI框架
有了基本目錄結(jié)構(gòu),你可能需要一個(gè)UI框架來處理反饋(modal、toast、動(dòng)畫)等交互,而不是重新埋頭寫,這里筆者推薦幾個(gè)小程序UI框架,具體的安裝(推薦npm安裝)移步各自文檔:
-
vant-weapp
vant的小程序版本,強(qiáng)烈推薦 -
iview-weapp
iview的小程序版本,已經(jīng)較久不更新 - weui 官方出品的微信風(fēng)格UI,但組件也相對(duì)少
構(gòu)建
編輯器
在發(fā)布和調(diào)試時(shí),你必須使用微信開發(fā)者工具進(jìn)行。但在編碼時(shí),你仍然可以使用自己喜好的編輯器,并安裝小程序的相關(guān)輔助插件來協(xié)助開發(fā)小程序, 微信會(huì)實(shí)時(shí)監(jiān)聽項(xiàng)目文件的修改并編譯到模擬器。

在小程序使用npm依賴
npm依賴的安裝和引入都和node環(huán)境下無差別,但你需在安裝好依賴或者有依賴更新后在微信開發(fā)者工具進(jìn)行npm構(gòu)建:



此時(shí)你會(huì)發(fā)現(xiàn)多了一個(gè)
miniprogram_npm目錄,此時(shí)我們就可以直接像node環(huán)境一樣直接引入npm依賴.
當(dāng)然,一些像
操作DOM等小程序不支持的對(duì)象的依賴是無法運(yùn)行的,需注意區(qū)分
ES6
你可以使用ES6,考慮到兼容性時(shí),無需借助babel來轉(zhuǎn)換為ES5,在右上角的詳情中勾選:

環(huán)境變量
在node環(huán)境,可以在打包時(shí)注入一個(gè)全局變量用來區(qū)分環(huán)境,以便處理在生產(chǎn)環(huán)境、開發(fā)環(huán)境下的不同邏輯:
#生產(chǎn)環(huán)境變量設(shè)置,如:
set NODE_ENV=production
#開發(fā)環(huán)境變量設(shè)置,如:
set NODE_ENV=development
但是小程序里,官方一直沒有做這方面的支持!所以你需在開發(fā)或者發(fā)布的時(shí)候手動(dòng)改一下用來充當(dāng)環(huán)境區(qū)分的變量:
//示例,config.js
const ENV = 'product'; //環(huán)境變量,手動(dòng)修改
module.exports = {
ENV,
DOMAIN: ENV === 'product'? 'a.com': 'dev.a.com',
TOKEN_KEY: ENV === 'product'? 'token' : 'token_dev',
//more...
}
編寫頁面
頁面的構(gòu)造,頁面的構(gòu)造需使用Page方法。詳細(xì)的屬性和生命周期你可以在文檔中查看。
為了頁面生效,你還必須在根目錄下的
app.json中的pages字段配上所有頁面的路徑, 并且pages數(shù)組第一個(gè)為小程序進(jìn)入的首個(gè)頁面:{ "pages": [ "pages/index/index", //進(jìn)入小程序時(shí)加載的第一個(gè)頁面 "pages/logs/logs" ], //more... }
新建頁面


頁面組成文件
假設(shè)新建的頁面叫page1,則會(huì)生成四個(gè)文件:
page1.js- Page方法的調(diào)用,處理頁面邏輯
page1.json- 當(dāng)前頁面的配置文件,比如設(shè)置頁面背景色、導(dǎo)航欄標(biāo)題等
page1.wxml- 頁面視圖,類似html語法,但你使用的標(biāo)簽需是小程序自有組件或者自定義組件名(下小節(jié)介紹);另外,view標(biāo)簽的地位相當(dāng)于html中的div;并且,指令和事件綁定的語法和vue不同(如:v-if對(duì)應(yīng)wx:if),具體介紹查看這里,模板那一塊看的不太明白可以先放下,因?yàn)槟愫芸赡芸梢允褂媒M件的方式替代它。
page1.wxss- 你幾乎可以把它當(dāng)成一個(gè)css文件來看,只是不需要手動(dòng)引入就會(huì)在當(dāng)前頁面生效。具體與css的差異可查看這里
注意: 小程序直接修改Page里的data并不會(huì)更新視圖,你需通過Page.prototype.setData(文檔搜索Page.prototype.setData查看)方法來設(shè)置value才可更新視圖
組件
在較新的小程序版本已經(jīng)支持自定義組件,但開發(fā)體驗(yàn)筆者認(rèn)為不如vue的單文件組件。這里做一些簡(jiǎn)單的對(duì)比介紹。
組件化


與頁面的構(gòu)成類似,組件的四個(gè)文件作用同頁面的一樣, 不同的主要有兩點(diǎn):
- 在
js文件里, 定義組件使用Component構(gòu)造器. - 在
json文件的component字段設(shè)為true,如:
{
"component": true
}
組件的調(diào)用
在頁面或者其他組件目錄下的json文件, 將所有引用的組件的標(biāo)簽名(key,自己取名)和文件路徑(value)傳入usingComponents
{
"usingComponents": {
"component-tag-name1": "/components/component-tag-name1/component-tag-name1",
"component-tag-name1": "/components/component-tag-name1/component-tag-name1"
//more component...
}
//more config...
}
并在wxml文件寫入組件標(biāo)簽及其傳入的參數(shù),事件等:
<component-tag-name1></component-tag-name1>
<component-tag-name2 option="{{yourOptionData}}" bindSomeEvent="eventCallback" />
下面介紹幾個(gè)傳入Component構(gòu)造器的幾個(gè)重要屬性.
properties
相當(dāng)于Vue中的props屬性, 約定父組件或者頁面?zhèn)魅氲膮?shù)格式:
Component({
properties: {
data1: {
type: Number, //參數(shù)的類型
value: 0 //默認(rèn)值
},
data2: {
type: Number,
optionalTypes: [String, Object], // 可以指定多個(gè)屬性的類型, 如果type也定義了的話或被計(jì)入optionalTypes, 這里的參數(shù)可以是 Number 、 String 、 Boolean 三種類型中的一種
observer: function(newVal, oldVal) { //參數(shù)值變化時(shí)的回調(diào)函數(shù), 與vue中`watch`的作用類似
}
}
}
})
behaviors
behavior相當(dāng)于vue中的mixins, 首先你得先構(gòu)造一個(gè)Behavior:
module.exports = Behavior({
behaviors: [], //behavior里可以引用其他behavior
properties: { //要共享的參數(shù)
myBehaviorProperty: {
type: String
}
},
data: { //要共享的data
myBehaviorData: {}
},
attached: function(){}, //要共享的生命周期處理邏輯
methods: { //要共享的方法
myBehaviorMethod: function(){}
}
})
然后在組件中引入:
// my-component.js
var myBehavior = require('my-behavior')
Component({
behaviors: [myBehavior],
properties: {
myProperty: {
type: String
}
},
//more...
})
observers
作用與vue中的watch屬性類似, 可以監(jiān)聽一個(gè)或多個(gè)數(shù)據(jù)變化并執(zhí)行回調(diào), 但語法上不同,詳情請(qǐng)移步文檔,這里不再贅述
生命周期
在小程序里, 組件的生命周期與頁面的生命周期并不相同(這是因?yàn)?在vue里,無論頁面和組件都是vue實(shí)例, 都可以看成組件, 而在小程序是不一樣的實(shí)例), 并且App注冊(cè)小程序時(shí)也有自己的生命周期.
小程序組件的生命周期與Vue中的生命周期時(shí)間節(jié)點(diǎn)基本一致, 只是命名上的差異;并且生命周期方法可以作為和data同級(jí)的屬性, 也可以放在lifetimes屬性里,但后者的優(yōu)先級(jí)更高:
Component({
lifetimes: {
attached: function() {
// 在組件實(shí)例進(jìn)入頁面節(jié)點(diǎn)樹時(shí)執(zhí)行
},
detached: function() {
// 在組件實(shí)例被從頁面節(jié)點(diǎn)樹移除時(shí)執(zhí)行
},
},
// 以下是舊式的定義方式,可以保持對(duì) <2.2.3 版本基礎(chǔ)庫的兼容
attached: function() {
// 在組件實(shí)例進(jìn)入頁面節(jié)點(diǎn)樹時(shí)執(zhí)行
},
detached: function() {
// 在組件實(shí)例被從頁面節(jié)點(diǎn)樹移除時(shí)執(zhí)行
},
// ...
})
另外組件還有一組生命周期可使用, 即組件所在頁面的生命周期(但并不是頁面所有生命周期方法, 也沒這個(gè)必要), 只有show,hide,resize; 所在頁面生命周期回調(diào)寫在pageLifetimes屬性:
Component({
pageLifetimes: {
show: function() {
// 頁面被展示
},
hide: function() {
// 頁面被隱藏
},
resize: function(size) {
// 頁面尺寸變化
}
}
})
組件通信和事件
與vue的通信方式一樣,事件是組件和頁面或者組件和組件主要和提倡的通信方式, 并且在小程序里, 也倡導(dǎo)單向數(shù)據(jù)流規(guī)范.
在頁面或者組件的wxml綁定事件:
<!-- 當(dāng)自定義組件觸發(fā)“myevent”事件時(shí),調(diào)用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以寫成 -->
<component-tag-name bind:myevent="onMyEvent" />
在組件里觸發(fā)事件:
Component({
properties: {},
methods: {
onTap: function(){
var myEventDetail = {} // detail對(duì)象,會(huì)作為參數(shù)傳遞到接收事件的方法里
var myEventOption = {} // 觸發(fā)事件的選項(xiàng), 主要控制事件是否可冒泡或者可有捕獲截?cái)?詳情可參考文檔
this.triggerEvent('myevent', myEventDetail, myEventOption) //觸發(fā)事件`myevent`
}
}
})
路由
組件介紹完畢, 小程序的路由機(jī)制不依賴其他插件, 由小程序自己控制. 并且開放出來的能力只限于作頁面跳轉(zhuǎn)并可攜帶參數(shù), 像路由變化回調(diào)等能力并不開放, 跳轉(zhuǎn)攜帶參數(shù)時(shí)可在目標(biāo)頁面的onLoad生命周期方法的參數(shù)中接收:
//more...
//假設(shè)當(dāng)前頁面路由為'/pages/demo/demo?a=1&b=2'
onLoad: function (options) { //options用于接收上個(gè)頁面?zhèn)鬟f過來的參數(shù)
console.log(options.a, options.b); // =>1, 2
})
//more...
路由跳轉(zhuǎn)的幾個(gè)方法你可以直接看文檔,這里不詳細(xì)介紹,只需注意理解的是頁面棧的概念(類似于瀏覽器環(huán)境中的history對(duì)象) ,和跳轉(zhuǎn)到tabbar頁面需用wx.switchTab方法.
全局變量/全局方法
在vue的開發(fā)過程中, 定義一個(gè)全局方法/屬性時(shí), 我們通常做法是掛到Vue對(duì)象的原型上:
Vue.prototype.data1 = 'xxx';
Vue.prototype.method1 = ()=>{
};
//在Vue實(shí)例里可以方便的訪問到:
//more...
mounted(){
console.log(this.data1);
this.method1()
}
//more...
當(dāng)然,一些經(jīng)常變動(dòng)和訪問的狀態(tài)我們會(huì)交給Vuex處理.
小程序里,全局變量/方法可以在App方法中定義:
App({
onLaunch (options) {
// Do something initial when launch.
},
globalData: { //全局變量放在globalData
data1: '1',
data2: '2'
},
//全局方法直接定義在根級(jí)別
method1(){
}
})
調(diào)用:
const app = getApp();
Page({
data: {
},
onLoad() {
app.method1();
console.log(app.data1, app.data2); //=>1, 2
}
//more...
})
另外, 你還可以將它們直接掛到global對(duì)象中, 但是一般不推薦這么做.
global.data1 = '';
調(diào)試
微信開發(fā)者工具是用開源的chrome內(nèi)核二次開發(fā), 在微信開發(fā)者工具中調(diào)試小程序, 跟在chrome調(diào)試web幾乎一樣; 可惜的是你并不能下載一些比如'vue.js Devtools'這樣的擴(kuò)展來協(xié)助調(diào)試, 所以很多時(shí)候你只能通過斷點(diǎn)和打印log來調(diào)試. 雖然工具提供了app級(jí)別的組件結(jié)構(gòu)和數(shù)據(jù)視圖, 但在筆者看來作用甚微(你可以體驗(yàn)一下):

測(cè)試
與vue的Vue Test Utils類似, 小程序提供miniprogram-simulate用于調(diào)試組件, 配合jest之類的測(cè)試框架可以實(shí)現(xiàn)組件的測(cè)試.
更多細(xì)節(jié)開發(fā)者可以慢慢摸索, 感謝閱讀!