前言
最近在公司做小程序開發,做兩個音視頻的頁面,解決方案使用的是騰訊雲提供的trtc-room組件,看了一下組件代碼,使用的是原生小程序實現的。公司開發小程序項目居多,wepy、mpvue框架都有項目在使用,加上前公司工作時,開發項目使用過uni-app開發小程序(當然,uni-app還可以打包成移動端app的包,安卓、iOS都可以),感覺開發小程序,總之都是基於原生小程序在搞事情,所以有些東西想實現的話,還是得看看原生是否支持,支持到什么程度,所以即使使用諸多框架,還是應該從原生小程序出發,之前感覺小程序開發嘛,代碼往上羅就行了(不會寫,照着寫),要說不會開發小程序吧,還能寫代碼,要說會吧,有些東西了解的不是很清楚,所以在解決有些問題的時候,不清楚的地方,解決問題的效率降低,解決問題提出的解決方案也會受限,所以我想決定好好看一遍原生小程序的文檔,對於前端開發來說,我覺得把小程序前面四個頁簽的內容看一遍應該就可以了(指南、框架、組件、API),至於工具(微信開發者工具)的使用,用着用着基本就會了。權當我個人總結。我打算指南,框架部分閱讀的時候總結點我自己之前不了解的干貨,至於組件和API部分,我覺得我總結的話也是搬磚不如直接看文檔來的痛快 。接下來,開始我的Ctrl C V。
微信小程序開發文檔:https://developers.weixin.qq.com/miniprogram/dev/framework/
微信公眾平台:https://mp.weixin.qq.com/
注:總結歸納的應該不是很全面,是我自己在開發小程序過程中,沒有詳細看文檔遺漏以及不了解的一些概念
小程序與普通網頁開發的區別
小程序的主要開發語言是JavaScript,小程序的開發同普通的網頁開發相比有很大的相似性。對於前端開發者而言,從網頁開發遷移到小程序的開發成本並不高,但是二者還是有些許區別的。
網頁開發渲染線程和腳本線程是互斥的,這也是為什么長時間的腳本運行可能會導致頁面失去響應,而在小程序中,二者是分開的,分別運行在不同的線程中。網頁開發者可以使用各種瀏覽器暴露出來的DOM API進行DOM操作,而小程序的邏輯層和渲染層是分開的,邏輯層運行在JSCore中,並沒有一個完整瀏覽器對象,因此缺少相關的DOM API和BOM API。這一區別導致前端開發非常熟悉的一些庫,例如:jQuery、Zepto等,在小程序中無法運行。同時JSCore的環境同NodeJS環境也是不盡相同,所以一些NPM的包在小程序中也是無法運行的。
網頁開發者需要面對的環境是各式各樣的瀏覽器,PC端需要面對IE、Chrome、Firefox等。在移動端需要面對的是iOS、安卓系統中各式的webview,而小程序開發過程中需要面對的是兩大操作系統的微信客戶端,,以及用於輔助開發的小程序開發者工具。
小程序中三大運行環境也是有所區別的,如下所示:
運行環境 | 邏輯層 | 渲染層 |
iOS | JSCore | WKWebView |
安卓 | V8 | chromium定制內核 |
小程序開發工具 | NWJS | Chrome WebView |
網頁開發者在開發網頁的時候,只需要使用瀏覽器,並且搭配上一些輔助工具或者編輯器即可。小程序的開發則有所不同,需要經過申請小程序賬號、安裝小程序開發者工具。配置項目等過程方可完成。
申請賬號
在小程序后台(微信公眾平台),可以進行你的小程序的權限管理、查看數據報表、發布小程序的操作。在菜單”開發“-”開發設置“中,可以得到小程序的AppID。小程序的AppID相當於小程序平台的一個身份證(在開發者工具中使用)。
小程序的版本
權限 | 說明 |
開發版本 | 使用開發者工具,可將代碼上傳到開發者版本中。開發版本只保留每人最新的一份上傳的代碼。 點擊提交審核,可將代碼提交審核。開發版本可刪除,不影響線上版本和審核中版本的代碼。 |
體驗版本 | 可以選擇某個開發版本作為體驗版,並且選取一份體驗版。 |
審核中版本 | 只能有一份代碼處於審核中。有審核結果后可以發布到線上,也可以直接提交審核,覆蓋原審核版本。 |
線上版本 | 線上所有用戶使用的代碼版本,該版本代碼在新版本代碼發布后被覆蓋更新。 |
小程序的發布
審核通過之后,管理員的微信總會收到小程序通過審核的通知。
小程序提供了兩種發布模式:全量發布和分階段發布。全量發布是指當點擊發布之后,所有用戶訪問小程序時都會使用當前最新的發布版本。分階段發布是指分不同時間段來控制部分用戶使用最新的發布版本,分階段發布我們也稱為灰度發布。一般來說,普通小程序發布時采用全量發布即可,當小程序承載的功能越來越多,使用的用戶也越來越多時,采用分階段發布是一個非常好的控制風險的辦法。
場景值
場景值用來描述用戶進入小程序的路徑,完整場景值的含義請查看場景值列表。
獲取場景值
對於小程序,可以在App的onLaunch和onShow,或wx.getLaunchOptionsSync中獲取上述場景值。
獲取來源信息的場景
部分場景值還可以獲取來源應用、公眾號或小程序的appId。
場景值 | 場景 | AppID含義 |
1020 | 公眾號profile頁相關的小程序列表 | 來源公眾號 |
1035 | 公眾號自定義菜單 | 來源公眾號 |
1036 | APP分享消息卡片 | 來源APP |
1037 | 小程序打開小程序 | 來源小程序 |
1038 | 從另一個小程序返回 | 來源小程序 |
1043 | 公眾號模板信息 | 來源公眾號 |
邏輯層App Service
小程序開發框架的邏輯層使用JavaScript引擎作為小程序提供開發者JavaScript代碼的運行環境以及微信小程序的特有功能。
邏輯層將數據進行處理后發送給視圖層,同時接受視圖層的事件反饋。
開發者寫的所有代碼最終會打包成一份JavaScript文件,並且在小程序啟動的時候運行,直到小程序銷毀。
在JavaScript的基礎上,我們增加了一些功能,以便小程序的開發:
- 增加App和Page方法,進行程序注冊和頁面注冊。
- 增加getApp和getCurrentPages方法,分別用來獲取App實例和當前頁面棧。
- 提供豐富的API,如掃一掃、支付等微信特有的功能。
- 提供模塊化能力,每個頁面有獨立的作用域。
注意:小程序框架的邏輯層並非運行在瀏覽器中,因此JavaScript在web中的一些能力都無法使用,如window、document等。
尺寸單位
rpx(responsive pixel):可以根據屏幕寬度進行自適應。規定屏幕寬度為750rpx。如在iPhone6上,屏幕寬度為375px,共有750個物理像素,
則750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
建議:開發微信小程序時設計師可以用iPhone6作為視覺稿的標准。
注意:在較小的屏幕上不可避免的會有一些毛刺,請在開發室盡量避免這種情況。
事件的使用方式
{ "type":"tap",//代表事件的類型 "timeStamp":895,//頁面打開到觸發事件所經過的毫秒數 "target": {//觸發事件的源組件 "id": "tapTest",//事件源組件的ID "dataset": {//事件源組件上由data- 開頭的自定義屬性組成的集合 "hi":"WeChat"//執行方法參數的解決方式 } }, "currentTarget": {//事件綁定的當前組件 "id": "tapTest",//當前組件的ID "dataset": {//當前組件上由data- 開頭自定義屬性組成的集合 "hi":"WeChat" } }, "detail": {//自定義事件所攜帶的數據。 "x":53, "y":14 }, "touches":[{ "identifier":0,//觸摸點的標識 "pageX":53,//距離文檔左上角的距離文檔的左上角為原點,橫向為x軸,縱向為y軸。 "pageY":14, "clientX":53,距離頁面可顯示區域(屏幕去除導航條)左上角距離,橫向為x軸,縱向為y軸。 "clientY":14 }], "changedTouches":[{ "identifier":0, "pageX":53, "pageY":14, "clientX":53, "clientY":14 }] }
使用wxs函數響應事件
支持使用wxs函數綁定事件,wxs函數接受2個參數,第一個是event,在原有的event的基礎上加了event.instance對象,第二個參數是ownerInstance,和event.instance一樣是一個ComponentDescriptor對象。
綁定並阻止事件冒泡
除bind外,也可以使用catch來綁定事件。與bind不同,catch會阻止事件向上冒泡。
響應顯示區域變化
在手機上啟用屏幕旋轉支持
從小程序基礎庫版本2.4.0開始,小程序在手機上支持屏幕旋轉。使小程序中的頁面支持屏幕旋轉的方法是:在app.json的window段中設置"pageOrientation":"auto",或者是在頁面的json文件中配置"pageOrientation":"auto"(意味着手機上單頁面配置屏幕旋轉)。從小程序2.5.0開始,pageOrientation還可以設置為landscape,表示固定為橫屏顯示。
在iPad上啟用屏幕旋轉支持
從小程序2.3.0開始,在iPad上運行的小程序可以支持屏幕旋轉。使小程序支持iPad屏幕旋轉的方法是在app.json中添加"resizable":true。注意:在iPad上不能單獨配置某個頁面是否支持屏幕旋轉。
JavaScript支持情況
運行限制
基於安全考慮,小程序中不支持動態執行js代碼:
- 不支持使用eval執行代碼。(上家公司為了實現業務需求,也找到了一個替代eval方法的解決方法。)
- 不支持使用new Function 創建函數。
小程序運行機制
前台/后台狀態
小程序啟動后,界面被展示給用戶,此時小程序處於前台狀態。
當用戶點擊右上角膠囊按鈕關閉小程序,或者按了設備home鍵離開微信時,小程序並沒有完全終止運行,而是進入了后台狀態,小程序還可以運行一小段時間。
當用戶再次進入微信或者再次打開小程序,小程序又會從后台進入前台。但如果用戶很久沒有進入小程序,或者系統資源緊張,小程序可能被銷毀,即完全終止運行。
小程序啟動
這樣,小程序啟動可以分為兩種情況,一種是冷啟動,一種是熱啟動。
- 冷啟動:如果用戶首次打開,或小程序銷毀后被用戶再次打開,此時小程序需要重新加載啟動,即冷啟動。
- 熱啟動:如果用戶已經打開過某小程序,然后在一定時間內再次打開該小程序,此時小程序並未被銷毀,只是從后台狀態進入前台狀態,這個過程就是熱啟動。
小程序更新機制
未啟動時更新
開發者在管理后台發布最新版小程序后,如果某個用戶本地有小程序的歷史版本,此時打開的可能還是舊版本。微信客戶端會有若干時機去檢查本地緩存的小程序有沒有更新版本,如果有則會靜默更新到新版本。總的來說,開發者在后台發布新版本后,無法立刻影響到所有現網用戶,但是最差的情況下,也會在發布之后24小時之內下發新版本信息到用戶。用戶下次打開時會先更新最新版本再打開。
啟動時更新
小程序每次冷啟動時都會檢查是否有最新版本,如果發現有新版本,將會異步下載新版本的代碼包,並同時用客戶端本地包進行啟動,即新版本的小程序需要等下一次冷啟動才可以應用。
如果需要馬上應用最新版本,可以使用 wx.getUpdateManager API 進行處理
自定義組件
Component構造器
Component構造器可用於自定義組件,調用Component構造器可以致電過組件的屬性、數據、方法。
- properties 組件的對外屬性,是屬性名到屬性設置的映射表
- data 組件的內部數據,和properties一同用於組件的模板渲染
- observers 組件數據字段監聽器 用於監聽properties 和data的變化
- methods 組件的方法 包括事件響應函數和任意的自定義方法
- behaviors 類似於mixins 和traits的組件間代碼復用機制
- created 組件生命周期函數-在組件實例剛剛被創建時執行,注意:此時不能調用setData
- attached 組件生命周期函數-在組件實例進入頁面節點節點樹時執行
- ready 組件聲明周期函數-在組件布局完成后執行
- moved 組件生命周期函數-在組件實例被移動到節點樹的另一個位置時執行
- detached 組件生命周期函數-在組件實例被從頁面節點樹移除時執行
- relations 組件間關系定義
- externalClasses 組件接受的外部樣式類
- options 一些選項
- lifetimes 組件生命周期對象
- pageLifetimes 組件所在頁面的生命周期對象
- definitionFilter 定義段過濾器,用於自定義組件擴展
Component({ behaviors: [], properties: { myProperty: { // 屬性名 type: String, value: '' }, myProperty2: String // 簡化的定義方式 }, data: {}, // 私有數據,可用於模板渲染 lifetimes: { // 生命周期函數,可以為函數,或一個在methods段中定義的方法名 attached: function () { }, moved: function () { }, detached: function () { }, }, // 生命周期函數,可以為函數,或一個在methods段中定義的方法名 attached: function () { }, // 此處attached的聲明會被lifetimes字段中的聲明覆蓋 ready: function() { }, pageLifetimes: { // 組件所在頁面的生命周期函數 show: function () { }, hide: function () { }, resize: function () { }, }, methods: { onMyButtonTap: function(){ this.setData({ // 更新屬性和數據的方法與更新頁面數據的方法類似 }) }, // 內部方法建議以下划線開頭 _myPrivateMethod: function(){ // 這里將 data.A[0].B 設為 'myPrivateData' this.setData({ 'A[0].B': 'myPrivateData' }) }, _propertyChange: function(newVal, oldVal) { } } })
組件間通信與事件
- 數據綁定:用於父組件向子組件的指定屬性設置數據
- 事件:用於子組件向父組件傳遞數據,可以傳遞任意數據
- 父組件還可以通過this.selectComponent()方法獲取子組件實例對象,這樣就可以直接訪問組建的任意數據和方法
自定義組件觸發事件需要使用triggerEvent方法,指定事件名、detail對象和事件選項。
this.triggerEvent('myEvent',myEventDetail,myEventOption)
觸發事件的選項包括:
選項名 | 類型 | 是否必填 | 默認值 | 描述 |
bubbles | Boolean | 否 | false | 事件是否冒泡 |
composed | Boolean | 否 | false | 事件是否可以穿越組建邊界,為false時,事件只能在引用組件的節點樹上觸發,不進入其他任何組件內部 |
capturePhase | Boolean | 否 | false | 事件是否擁有捕獲階段 |
當組件觸發生命周期時,上例生命周期函數執行順序為:
[my-behavior] created
[my-component] created
[my-behavior] attached
[my-component] attached
[my-behavior] ready
[my-component] ready
存儲
同一個微信用戶,同一個小程序 storage 上限為 10MB。
開放能力
小程序可以通過微信官方提供的登錄能力方便的獲取微信提供的用戶身份標識,快速的建立小程序內的用戶體系。(MS的時候,問到小程序可能會被問到)
登錄流程時序
說明:
- 調用wx.login()獲取臨時登錄憑證code,並回傳到開發者服務器。
- 調用auth.code2Session接口,換取用戶唯一標識OpenID和會話秘鑰session_key
之后開發者服務器可以根據表示來生成自定義登錄狀態,用於后續業務邏輯中前后端交互時識別用戶身份
注意:
- 會話密鑰
session_key
是對用戶數據進行 加密簽名 的密鑰。為了應用自身的數據安全,開發者服務器不應該把會話密鑰下發到小程序,也不應該對外提供這個密鑰。 - 臨時登錄憑證 code 只能使用一次
UnionID 機制說明
如果開發者擁有多個移動應用、網站應用、和公眾帳號(包括小程序),可通過 UnionID 來區分用戶的唯一性,因為只要是同一個微信開放平台帳號下的移動應用、網站應用和公眾帳號(包括小程序),用戶的 UnionID 是唯一的。換句話說,同一用戶,對同一個微信開放平台下的不同應用,UnionID是相同的。
只要是同一個微信開放平台帳號下的移動應用、網站應用和公眾帳號(包括小程序),用戶的 UnionID 是唯一的。換句話說,同一用戶,對同一個微信開放平台下的不同應用,UnionID是相同的。
UnionID獲取途徑
綁定了開發者帳號的小程序,可以通過以下途徑獲取 UnionID。
-
調用接口 wx.getUserInfo,從解密數據中獲取 UnionID。注意本接口需要用戶授權,請開發者妥善處理用戶拒絕授權后的情況。
-
如果開發者帳號下存在同主體的公眾號,並且該用戶已經關注了該公眾號。開發者可以直接通過 wx.login +
code2Session
獲取到該用戶 UnionID,無須用戶再次授權。 -
如果開發者帳號下存在同主體的公眾號或移動應用,並且該用戶已經授權登錄過該公眾號或移動應用。開發者也可以直接通過 wx.login +
code2Session
獲取到該用戶 UnionID ,無須用戶再次授權。 -
用戶在小程序(暫不支持小游戲)中支付完成后,開發者可以直接通過
getPaidUnionId
接口獲取該用戶的 UnionID,無需用戶授權。注意:本接口僅在用戶支付完成后的5分鍾內有效,請開發者妥善處理。 -
小程序端調用雲函數時,如果開發者帳號下存在同主體的公眾號,並且該用戶已經關注了該公眾號,可在雲函數中通過 cloud.getWXContext 獲取 UnionID。
-
小程序端調用雲函數時,如果開發者帳號下存在同主體的公眾號或移動應用,並且該用戶已經授權登錄過該公眾號或移動應用,也可在雲函數中通過 cloud.getWXContext 獲取 UnionID。
授權
部分接口需要經過用戶授權同意才能調用。我們把這些接口按使用范圍分成多個 scope
,用戶選擇對 scope
來進行授權,當授權給一個 scope
之后,其對應的所有接口都可以直接使用。
此類接口調用時:
- 如果用戶未接受或拒絕過此權限,會彈窗詢問用戶,用戶點擊同意后方可調用接口;
- 如果用戶已授權,可以直接調用接口;
- 如果用戶已拒絕授權,則不會出現彈窗,而是直接進入接口 fail 回調。請開發者兼容用戶拒絕授權的場景。
獲取用戶授權設置
開發者可以使用 wx.getSetting 獲取用戶當前的授權狀態。
打開設置界面
用戶可以在小程序設置界面(「右上角」 - 「關於」 - 「右上角」 - 「設置」)中控制對該小程序的授權狀態。
開發者可以調用 wx.openSetting 打開設置界面,引導用戶開啟授權。
提前發起授權請求
開發者可以使用 wx.authorize 在調用需授權 API 之前,提前向用戶發起授權請求。
scope 列表
scope | 對應接口 | 描述 |
---|---|---|
scope.userInfo | wx.getUserInfo | 用戶信息 |
scope.userLocation | wx.getLocation, wx.chooseLocation | 地理位置 |
scope.userLocationBackground | wx.startLocationUpdateBackground | 后台定位 |
scope.address | wx.chooseAddress | 通訊地址 |
scope.invoiceTitle | wx.chooseInvoiceTitle | 發票抬頭 |
scope.invoice | wx.chooseInvoice | 獲取發票 |
scope.werun | wx.getWeRunData | 微信運動步數 |
scope.record | wx.startRecord | 錄音功能 |
scope.writePhotosAlbum | wx.saveImageToPhotosAlbum, wx.saveVideoToPhotosAlbum | 保存到相冊 |
scope.camera | camera 組件 | 攝像頭 |
示例代碼
// 可以通過 wx.getSetting 先查詢一下用戶是否授權了 "scope.record" 這個 scope wx.getSetting({ success(res) { if (!res.authSetting['scope.record']) { wx.authorize({ scope: 'scope.record', success () { // 用戶已經同意小程序使用錄音功能,后續調用 wx.startRecord 接口不會彈窗詢問 wx.startRecord() } }) } } })
第一次進入小程序執行wx.authorize()方法會調起授權彈框,當誤操作為允許授權,只能由開發者調用 wx.openSetting 打開設置界面,引導用戶開啟授權。
打開 App
此功能需要用戶主動觸發才能打開 APP,所以不由 API 來調用,需要用 open-type
的值設置為 launchApp
的 button 組件的點擊來觸發。
當小程序從 APP 分享消息卡片的場景打開(場景值 1036,APP 分享小程序文檔 iOS / Android) 或從 APP 打開的場景打開時(場景值 1069),小程序會獲得打開 APP 的能力,此時用戶點擊按鈕可以打開分享該小程序卡片/拉起該小程序的 APP。即小程序不能打開任意 APP,只能 跳回
APP。
<button open-type="launchApp" app-parameter="wechat" binderror="launchAppError">打開APP</button>
Page({ launchAppError (e) { console.log(e.detail.errMsg) } })
引用
WXML 提供兩種文件引用方式import
和include
。
import
import
可以在該文件中使用目標文件定義的template
,如:
在 item.wxml 中定義了一個叫item
的template
:
<!-- item.wxml --> <template name="item"> <text>{{text}}</text> </template>
在 index.wxml 中引用了 item.wxml,就可以使用item
模板:
<import src="item.wxml"/> <template is="item" data="{{text: 'forbar'}}"/>
import 的作用域
import 有作用域的概念,即只會 import 目標文件中定義的 template,而不會 import 目標文件 import 的 template。
如: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"/>
include
include
可以將目標文件除了 <template/>
<wxs/>
外的整個代碼引入,相當於是拷貝到 include
位置,如:
<!-- index.wxml --> <include src="header.wxml"/> <view> body </view> <include src="footer.wxml"/>
<!-- header.wxml --> <view> header </view>
<!-- footer.wxml --> <view> footer </view>
未完待續...