uni-app 中實現 onLaunch 非同步回撥後執行 onLoad 最佳實踐

江陽小道發表於2022-05-23

前言

好久沒寫部落格了,由於公司業務需要,最近接觸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:大家有更好的解決方案,歡迎在評論區交流。

參考資料

  1. uni-app 中利用 Promise 實現 onLaunch 非同步回撥後執行 onLoad
  2. 小程式app.onLaunch與page.onLoad非同步問題的最佳實踐
  3. 代理生命週期 | uni-simple-router

相關文章