小程序在內測的時候就已經開始玩了,不過最開始的時候以爲,這sx東西東西怎麼這麼坑的樣子,網絡請求竟然不是返回Promise而是用Callback的方式, 傳值竟然不能把值寫在方法裏只能用dataset,在這個全面組件化的大環境下竟然不支持組件化...php
其實最開始主要是書寫時習慣的問題,秉承着我又不作小程序開發,就先忍着你的態度聽任無論了。然而天有不測風雲,最近由於業務的需求不得不作小程序相關的開發,我就倔脾氣果斷就不能忍了。果斷的把不爽的地方改爲能按我喜歡的方式來走,其中還遇到了一些其餘的坑,一個個慢慢填,並把這些記錄下來整理成了文章。html
網絡請求小程序提供了wx.request,這個是我最想吐槽的點, 仔細看一下 api,這貨不就是n年前的$.ajax
嗎,好古老啊。vue
// 官方例子
wx.request({
url: 'test.php', //僅爲示例,並不是真實的接口地址
data: {
x: '' ,
y: ''
},
header: {
'content-type': 'application/json' // 默認值
},
success: function(res) {
console.log(res.data)
}
})
複製代碼
如今仍是隻有一個請求就已經感受寫的很長了,若是一個頁面須要多個請求呢,若是請求的順序還有要求呢該怎麼辦,各類嵌套又臭又長,若是要求因此請求都完成以後再顯示界面呢 瞬間懵逼。react
這個時候我弱弱的看了一眼小程序的JS版本的支持,歐耶,比較良心的支持ES6。也就是說咱們能夠將其改爲Promise是可能的。接下來看我如何改造。ios
/* utils/api.js 自定義網絡請求 */
const baseURL = 'https://yourapi.com' // 本身後臺API地址
const http = ({ url = '', params = {}, ...other} = {}) => {
wx.showLoading({
title: '加載中...'
})
let time = Date.now()
console.log(`開始:${time}`)
return new Promise((resolve, reject) => {
wx.request({
url: getUrl(url),
data: params,
header: getHeader(),
...other,
complete: (res) => {
wx.hideLoading()
console.log(`耗時:${Date.now() - time}`)
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data)
} else {
reject(res)
}
}
})
})
}
const getUrl = url => {
if (url.indexOf('://') == -1) {
url = baseURL + url
}
return url
}
const getHeader = () => {
try {
var token = wx.getStorageSync('token')
if (token) {
return { 'token': token }
}
return {}
} catch (e) {
return {}
}
}
module.exports = {
baseURL,
get (url, params = {}) {
return http({
url,
params
})
},
post(url, params = {}) {
return http({
url,
params,
method: 'post'
})
},
put(url, params = {}) {
return http({
url,
params,
method: 'put'
})
},
// 這裏不能使用 delete, delete爲關鍵字段
myDelete(url, params = {}) {
return http({
url,
params,
method: 'delete'
})
}
}
複製代碼
接了下來咱們就能夠正常的時候用了,寫一下簡單的例子吧ajax
const api = require('../../utils/api.js')
// 單個請求
api.get('list').then(res => {
console.log(res)
}).catch(e => {
console.log(e)
})
// 一個頁面多個請求
Promise.all([
api.get('list'),
api.get(`detail/${id}`)
]).then(result => {
console.log(result)
}).catch(e => {
console.log(e)
})
複製代碼
若是習慣了 fetch 以及 axios 的朋友應該都會比較喜歡這種寫法。 在作網絡請求的時候還遇到一個問題,登陸受權的問題。json
作一個應用,確定避免不了登陸操做。用戶的我的信息啊,相關的收藏列表等功能都須要用戶登陸以後才能操做。通常咱們使用token作標識。而後又會涉及到token不存在,用戶第一次登陸,token過時後,從新登陸等問題。比較常規的操做是直接跳轉到登陸頁面。axios
而後坑就出現了,小程序並無登陸界面,使用的是 wx.login
。wx.login
會獲取到一個 code,拿着該 code 去請求咱們的後臺會最後返回一個token到小程序這邊,保存這個值爲 token
每次請求的時候帶上這個值。(詳情能夠查看小程序登陸。)小程序
然而僅僅這樣就夠了嗎? 很顯然並非。通常還須要把用戶的信息帶上好比用戶微信暱稱,微信頭像等,這時候就須要使用 wx.getUserInfo
,這裏涉及到一個用戶受權的問題,留一個坑接下來再解決。帶上用戶信息就夠了嘛? too young too simple!咱們的項目不可能只有小程序,相應的微信公衆平臺可能還有相應的App,咱們須要把帳號系統打通,讓用戶在咱們的項目中的帳戶是同一個。這就須要用到微信開放平臺提供的 UnionID
。後端
ps.基於小程序在微信中的易傳播性, 爲了鼓勵用戶去傳播分享通常還會提供邀請獎勵機制。可是微信這邊又會對誘導分享進行和諧處理。視狀況慎用。(本文會在例子上加上該功能)
看到這,是否是以爲頭都大了,就一個小小的登陸功能坑這麼多。 年輕的我瑟瑟發抖~~~。慢慢開始填吧。先上登陸代碼
/* utils/api.js 自定義網絡請求 */
...
function login() {
return new Promise((resolve, reject) => {
// 先調用 wx.login 獲取到 code
wx.login({
success: res => {
// 再調用 wx.getUserInfo 獲取到用戶的一些信息 (一些基本信息,以及生成UnionID 所用到的信息 好比 rawData, signature, encryptedData, iv)
wx.getUserInfo({
// 若獲取不到用戶信息 (最大多是用戶受權不容許,也有多是網絡請求失敗,但該狀況不多)
fail: (e) => {
reject(e)
},
success: ({ rawData, signature, encryptedData, iv }) => {
let param = {
code: res.code,
rawData,
signature,
encryptedData,
iv
}
// 如有邀請ID
try {
let invite = wx.getStorageSync('invite')
if (invite) {
param.invite = invite
}
} catch (e) {
}
// 登陸操做
http({
url: 'login',
params: param,
method: 'post'
}).then(res => {
// 該爲咱們後端的邏輯 若code > 0爲登陸成功,其餘狀況皆爲異常 (視自身狀況而定)
if (res.code > 0) {
// 保存用戶信息
wx.setStorage({
key: 'userinfo',
data: res.data
})
wx.setStorage({
key: "token",
data: res.message,
success: () => {
resolve(res)
}
})
} else {
reject(res)
}
}).catch(error => reject(error))
}
})
}
})
})
}
...
複製代碼
根據上面的代碼,能夠很清楚的看到,若用戶在登陸的時候不容許小程序獲取他的用戶信息以後才能繼續。若用戶在這個時候點拒絕了呢, 會怎麼樣? 一片空白!~~ What’s the fuck! 怎麼什麼都沒有!垃圾破小程序~~ 冷靜點的用戶也會百臉懵逼狀。我是誰?我該怎麼辦? 也許你會以爲,用戶點容許就行了啊,怎麼會這麼笨,這種用戶確定不會多之類的話。我在咱們小程序中加了統計大約有 20%
的用戶點了拒絕, 若是後續咱們沒有作任何引導的話,這 20% 的用戶就會永遠失去。這個後果咱們徹底不能接受。
通過咱們的小組研究與討論,給出了一下的一套方案。
具體代碼能夠以下表示,用到了 wx.openSetting
來跳轉到設置受權界面。
/* index.js */
// 如有用戶信息存在則繼續
Page({
onLoad () {
wx.getStorage({
key: 'userinfo',
success: (res) => {
this.setUserinfo(res)
},
fail: (res) => {
api.login().then((res) => {
this.setUserinfo(res)
}).catch(e => {
if (e.errMsg && e.errMsg === 'getUserInfo:fail auth deny') {
this.setData({
isauth: false
})
}
})
}
})
},
toSetting() {
wx.openSetting({
success: (res) => {
this.setData({
isauth: res.authSetting['scope.userInfo']
})
if (res.authSetting['scope.userInfo']) {
api.login().then((res) => {
this.setUserinfo(res)
})
}
}
})
}
})
// setUserinfo 就是對用戶信息作一下處理 不具體展開了
/* index.wxml */
<view class="unauth" wx:if="{{!isauth}}">
<image class="unauth-img" src="../../images/auth.png"></image>
<text class="unauth-text">檢查到您沒打開受權</text>
<button class="color-button unauth-button" bindtap="toSetting">去設置</button>
</view>
<view class="container" wx:else>
...
</view>
複製代碼
登陸獲取到的 token 是有時效的,失效過了會怎麼樣 ? 若是後臺小夥伴嚴格按照 REST API 規範設計接口 API 的話,他會給咱們返回一個錯了 http code 爲 401。(常見的Http Code以及相關代碼的意義本文就不作展開了,不瞭解的小夥伴能夠自行 google 百度一下。)401 以後咱們就須要對該Code進行相應的處理。能夠以下這麼寫
api.get('list').then(res => {
/* do something */
}).catch(e => {
if (res.statusCode === 401) {
api.login().then(() => {
api.get('list').then(res => {
/* do something */
})
})
}
})
複製代碼
看起來沒什麼問題,也完成需求了。可是會發現這有很大的問題。
/* do something */
也是重複的 (固然把這整塊內容都提取出來,這裏就調用也行。不過仍是想把調用這邊也省略掉 ^-^ )屢一下咱們要實現的目標。
這個體現出把本身封裝一個網絡請求的好處, 咱們能夠直接改寫 api.js 中的 http 方法裏對 error 的處理就好。上代碼:
const http = ({ url = '', params = {}, ...other} = {}) => {
wx.showLoading({
title: '加載中...'
})
let time = Date.now()
console.log(`開始:${time}`)
return new Promise((resolve, reject) => {
wx.request({
url: getUrl(url),
data: params,
header: getHeader(),
...other,
complete: (res) => {
wx.hideLoading()
console.log(`耗時:${Date.now() - time}`)
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data)
} else if (res.statusCode === 401) {
// 401 爲鑑權失敗 很大多是token過時
// 從新登陸 而且重複請求
login().then(res => {
http({ url, params, ...other }).then(res => {
resolve(res)
})
})
} else {
reject(res)
}
}
})
})
}
複製代碼
網絡請求這塊,算目前開發項目中必不可少的一塊。可是例如 小程序,vue, react, weex 等其實都有一套本身的或者本身推薦的一套API以及相應的寫法。沒一個都按照他推薦的來寫,其實挺蛋疼的,用着很不爽。把他們的API封裝一下,暴露出來統一的API, 給本身用或者尤爲是給本身團隊的小夥伴用就比較方便,少了不少重複學習成本,而且由於統一的API帶來的統一的格式也是很大的一個好處。
說到小程序要弄清楚的東西很多,有些坑我還在摸索怎麼處理。好比小程序的組件化,全局變量的使用(什麼值能夠放在app.js裏),html標籤的轉換等,後續弄透了我會再出來獻醜的。