前言
好久沒寫部落格了,由於公司業務需要,最近接觸uiapp比較多,一直想著輸出一些相關的文章。正好最近時間富餘,有機會來一波輸出了。
問題描述
在使用 uni-app 開發專案時,會遇到需要在 onLaunch 中請求介面返回結果,並且此結果在專案各個頁面的 onLoad 中都有可能使用到的需求,比如微信小程式在 onLaunch 中進行登入後取得 openid 並獲得 token,專案各頁面需要帶上該 token 請求其他介面。
問題原因
在onLaunch 中的請求是非同步的,也就是說在執行 onLaunch 後頁面 onLoad 就開始執行了,而不會等待 onLaunch 非同步返回資料後再執行,這就導致了頁面無法拿到 onLaunch 中非同步獲取的資料。
解決問題
知道問題原因之後,解決起來就容易了。作為資深白嫖黨,先是搜尋了相關資料,發現了下面的解決方案。
解決方案一
既然在onLaunch中請求是非同步的原因導致這個問題,那改成同步的不就行了,這裡利用Promise來解決這個問題。步驟如下。
步驟一
在 main.js 中增加如下程式碼:
Vue.prototype.$onLaunched = new Promise(resolve => {
Vue.prototype.$isResolve = resolve
})
步驟二
在 App.vue 的 onLaunch
中增加程式碼 this.$isResolve()
,具體如下:
onLaunch () {
uni.login({
provider: 'weixin',
success: loginRes => {
login({ // 該介面為我們自己寫的獲取 openid/token 的介面,請替換成自己的
code: loginRes.code
}).then(res => {
try {
console.info(res.data.token)
uni.setStorageSync('token', res.data.token)
this.$isResolve()
} catch (e) {
console.error(e)
}
})
}
})
}
步驟三
在頁面 onLoad
中增加程式碼 await this.$onLaunched
,具體如下:
async onLoad(option) {
await this.$onLaunched
let token = ''
try {
token = uni.getStorageSync('token')
} catch(e) {
console.error(e)
}
// 下面就可以使用 token 呼叫其他相關介面
}
有了這個解決方案,我就開始在實際專案中是用來了。但隨著專案的複雜度增加,發現這個方案使用起來有一些弊端。每個頁面都需要在 onLoad
中增加程式碼程式碼也太煩人了。
有沒有更優雅的方案呢?繼續查詢資料,有個解決方案是定製一個頁面鉤子,然後註冊全域性的非同步任務,定義鉤子的觸發條件,滿足條件時即可自動執行頁面裡相關的鉤子。相關方案見參考資料2。
但這個方案我也不太滿意,仍然需要在頁面新增一些函式去響應請求。後面突然想到,可以監聽路由變動,在路由跳轉之前完成請求。
解決方案二(推薦)
正好專案中用到了uni-simple-router外掛,提供了全域性前置守衛事件beforeEach
,其本質是代理了所有的生命週期,讓生命週期更加可控,這樣就可以很好的解決我們面臨的問題了。步驟如下:
步驟一
在 route.js 增加如下程式碼:
// 登入(可放在公共函式裡面)
const login = () => {
return new Promise(function(resolve, reject) {
uni.login({
provider: 'weixin',
success: loginRes => {
login({ // 該介面為我們自己寫的獲取 openid/token 的介面,請替換成自己的
code: loginRes.code
}).then(res => {
resolve(res);
}, err => {
reject(err);
})
},
fail: err => {
reject(err);
}
});
});
}
// 獲取token(可放在公共函式裡面)
const getToken = () => {
let token = ''
try {
token = uni.getStorageSync('token')
} catch(e) {
console.error(e)
}
return token;
}
// 是否登入
let hasLogin = false;
router.beforeEach(async (to, from, next) => {
// 首次進來,沒有登入並且token不存在先請求資料
if(!hasLogin&&!getToken()){
const res = await login();
try {
console.info(res.data.token)
uni.setStorageSync('token', res.data.token)
hasLogin = true
} catch (e) {
console.error(e)
}
}
next()
})
步驟二
在頁面 onLoad
中直接就可以獲取 token
並使用,具體如下:
onLoad(option) {
let token = ''
try {
token = uni.getStorageSync('token')
} catch(e) {
console.error(e)
}
// 下面就可以使用 token 呼叫其他相關介面
}
這個解決方案就靈活很多,只需要在 route.js 中寫入程式碼,其他任意地方都可以呼叫。不用擔心新增頁面忘記相關方法的引入,更加靈活自由。
由於這個解決方案基於uni-simple-router外掛,在使用前需要引入這個外掛。如果不想引入外掛,可以自行實現程式碼生命週期功能。
PS:大家有更好的解決方案,歡迎在評論區交流。