背景
- 小程式歷史棧最多隻支援10層
- 當小程式業務比較複雜時,就很容易超過10層。
- 當超過10層後,有的機型是點選無反應,有的機型會出現一些未知錯誤
為了解決這些問題,我們引入了無限層級路由方案。
方案
首先宣告一下,最初方案並不是我提出的,是我司內部一位清華學霸提出的。但他們是基於wepy框架做的處理,由於我們用的是mpvue,所以對這個方案上做了修改,同時不依賴於框架。
雖然是改造版,但原理是一樣的,下面我來介紹一下修改後的方案。
幾個關鍵點:
- 9層(含9層)以內時:走小程式自己的歷史棧就ok了,跳轉時候更新一下邏輯棧,這沒啥可說的
- 從9層跳轉10層:需要把第9層重定向到中轉頁,再由中轉頁跳轉到10層
- 10層以後跳轉:在navigateTo方法中處理,到10層之後,再跳轉就第10層頁面一直做redirectTo(重定向)操作了
- 10層以上返回:會返回到中轉頁,由中轉頁判斷,具體返回到哪個頁面,然後navigateTo(跳轉)過去
- 從10層返回到9層:返回到中轉頁,將中轉頁redirectTo(重定向)到第9層頁面
- 9層內的返回:直接返回就好了,返回時候不會更新邏輯棧,但沒有關係,因為只有中轉頁才會用到邏輯棧
- 邏輯棧更新機制:
- 跳轉、返回中轉頁時更新
- navigateTo時更新
- redirectTo時更新
- reLaunch時更新
- navigateBack時更新
圖示:
- 使用者操作
- 小程式歷史棧
- js邏輯棧:自行維護的js路由棧
- “中”表示中轉頁
- “1 2 3 4 5 6 7 8 9 A B C”表示不同的頁面路徑
使用者操作 | 小程式歷史棧 | js邏輯棧 | 後續操作 |
---|---|---|---|
1 2 3 4 5 6 7 8 | 1 2 3 4 5 6 7 8 | 1 2 3 4 5 6 7 8 | 跳轉頁面9 |
1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 | 跳轉頁面A |
1 2 3 4 5 6 7 8 9 A | 1 2 3 4 5 6 7 8 中 A | 1 2 3 4 5 6 7 8 9 A | 跳轉頁面B |
1 2 3 4 5 6 7 8 9 A B | 1 2 3 4 5 6 7 8 中 B | 1 2 3 4 5 6 7 8 9 A B | 跳轉頁面C |
1 2 3 4 5 6 7 8 9 A B C | 1 2 3 4 5 6 7 8 中 C | 1 2 3 4 5 6 7 8 9 A B C | 返回 |
1 2 3 4 5 6 7 8 9 A B | 1 2 3 4 5 6 7 8 中 B | 1 2 3 4 5 6 7 8 9 A B | 返回 |
1 2 3 4 5 6 7 8 9 A | 1 2 3 4 5 6 7 8 中 A | 1 2 3 4 5 6 7 8 9 A | 返回 |
1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 | 1 2 3 4 5 6 7 8 9 | 返回(邏輯棧不更新) |
1 2 3 4 5 6 7 8 | 1 2 3 4 5 6 7 8 | 1 2 3 4 5 6 7 8 9 | 返回(邏輯棧不更新) |
1 2 3 4 5 6 7 | 1 2 3 4 5 6 7 | 1 2 3 4 5 6 7 8 9 | 返回(邏輯棧不更新) |
1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 7 8 9 | 返回(邏輯棧不更新) |
1 2 3 4 5 | 1 2 3 4 5 | 1 2 3 4 5 6 7 8 9 | 跳轉頁面6 |
1 2 3 4 5 6 | 1 2 3 4 5 6 | 1 2 3 4 5 6 | 跳轉頁面7 |
1 2 3 4 5 6 7 | 1 2 3 4 5 6 7 | 1 2 3 4 5 6 7 | ... |
到這裡細心的讀者可能已經發現:
之前跳轉操作和10層以上的返回操作都會更新邏輯棧,到了10層以內的返回操作就不會更新邏輯棧了。
原因:
這塊也是我們對原有方案的主要改造點。因為到了10層以內,所有的返回和跳轉都由微信系統歷史棧接管了。
我們只要保證使用者在通過api進行跳轉操作時更新就可以了。而且,自己維護的邏輯路由棧實際上只有中轉頁才會用到。
這樣也就不用在每個頁面都要註冊onUnload鉤子去實時更新返回時的路由資訊了。把更新路由資訊的邏輯都放到了api呼叫這一層。業務開發時完全不用關心。
示意程式碼
lib/navigator/Navigator.js (自己封裝的跳轉方法, History.js程式碼省略了)
...
import History from '@/lib/navigator/History'
const MAX_LEVEL = 10 // 小程式支援開啟的頁面層數
export default class Navigator {
// 中轉頁面路徑
static curtainPage = '/pages/curtain/curtain/main'
// 最大頁數
static maxLevel = MAX_LEVEL
// 邏輯棧
static _history = new History({
routes: [{ url: '' }],
correctLevel: MAX_LEVEL - 2
})
...
/**
* 開啟新頁面
* @param {Object} route 頁面配置,格式同wx.navigateTo
*/
@makeMutex({ namespace: globalStore, mutexId: 'navigate' }) // 避免跳轉相關函式併發執行
static async navigateTo (route) {
console.log('[Navigator] navigateTo:', route)
// 更新邏輯棧
Navigator._history.open({ url: route.url })
let curPages = getCurrentPages()
// 小於倒數第二層時,直接開啟
if (curPages.length < MAX_LEVEL - 1) {
await Navigator._secretOpen(route) // 就是呼叫wx.navigateTo
// 倒數第二層開啟最後一層
} else if (curPages.length === MAX_LEVEL - 1) {
const url = URL.setParam(Navigator.curtainPage, { url: route.url })
await Navigator._secretReplace({ url }) // wx.redirectTo 到中轉頁,再由中轉頁跳轉到第10層頁面
// 已經達到最大層數,直接最後一層重定向
} else {
await Navigator._secretReplace(route) // wx.redirectTo 第10層頁面直接重定向
}
}
/**
* 完整歷史記錄
* @return {Array}
*/
static get history () {
return Navigator._history.routes
}
/**
* 更新路由
* @param {Object} config 自定義配置,可配置項參見 _config 相關欄位及註釋
*/
static updateRoutes (routes = []) {
this._history._routes = routes
}
...
}
複製程式碼
中轉頁程式碼 /pages/curtain/curtain/index.vue
<template>
<div class="main"></div>
</template>
<script>
import Navigator from '@/lib/navigate/Navigator'
// query引數
let opts = null
// 是否為返回操作
let isBack = false
export default {
onLoad (options) {
// 快取引數
opts = options
// 執行onLoad生命週期,認為是跳轉或者重定向操作
isBack = false
},
onShow () {
// 跳轉、重定向操作時,邏輯棧的狀態會在跳轉函式裡更新
if (!isBack) {
const url = decodeURIComponent(opts.url)
// 再返回時認為是返回操作
isBack = true
// 跳轉操作
if (opts.type === 'navigateTo') {
Navigator._secretOpen({ url }) // 相當直接執行wx.navigateTo,不會更新邏輯棧
// 重定向
} else if (opts.type === 'redirectTo') {
Navigator._secretReplace({ url }) // 相當直接執行wx.redirectTo,不會更新邏輯棧
}
// 返回操作
} else {
// 獲取邏輯棧
let routes = Navigator.history
// 如果10層之外的返回,用navigateTo操作
// 如果是10層返回到9層,用redirectTo操作
const operation = (routes.length === Navigator.maxLevel) ? 'redirectTo' : 'navigateTo'
// 獲取要返回的頁面路由
let preRoute
if (operation === 'navigateTo') {
// 移除邏輯層中後兩個元素:
// 移除最後一個是因為要返
// 移除倒數第二個是因為,跳轉到倒數第二個頁面時會重新插入邏輯棧
preRoute = routes.splice(routes.length - 2, 2)[0]
} else {
// 重定向時只移除最後一個元素
preRoute = routes[routes.length - 2]
routes.splice(routes.length - 1, 1)
}
// 更新邏輯棧
Navigator.updateRoutes(routes)
// 執行自己包裝的跳轉、重定向方法,該操作會更新邏輯棧
Navigator[operation](preRoute)
}
}
}
</script>
<style lang="scss">
.main {
background-color: $white-color;
}
</style>
複製程式碼
原理就是這樣,但是有幾點需要注意:
- 業務程式碼中需要呼叫自己封裝的跳轉方法
切記不要直接呼叫wx的api,也不要使用元件,這樣是沒法更新js邏輯棧的,正確跳轉方式如:Navigator.navigateTo({ url: 'xxx' })。
- 跳轉時要及時更新js邏輯棧(更新時機如上所述),因為這會直接影響中轉頁的跳轉邏輯
這個方案最大的優點在於不用監聽頁面解除安裝時對邏輯棧的更新,無需在每個頁面里加入更新邏輯棧程式碼。
OK,這次就介紹這麼多,有問題或者有更好的方案,可以留言溝通,大家相互學習。