微信小程序页面栈超过【10层】之后无法打开其他页面原因
微信小程序路由跳转有个隐藏的坑,就是
wx.navigateTo
打开新页面,最多只能打开10个,超过10个之后就没反应,控制台也不会报错。
方案一:简单粗暴…navigateTo
不行,用redirectTo
小程序路由跳转的方式有五种,分别是
wx.navigateTo
(打开新页面,新页面入栈)、wx.redirectTo
(重定向,当前页面出栈,新页面入栈)、wx.navigateBack
(返回,页面不断出栈,直到目标返回页)、wx.switchTab
(切换tab页面,页面全部出栈,只留下新的 Tab 页面)、wx.reLaunch
(页面全部出栈,只留下新的页面)由此产生了第一种方式,当页面栈超过 10 时,直接用
redirectTo
。但这样太粗暴了,显然很多场景是需要保留访问过的页面的,由此有了方案一的升级版。
方案一升级版:根据页面栈决定当前跳转方式
每次跳转先去页面栈中查找目标页面是否已经访问过,如果访问过则用
wx.navigateBack
返回,如果没有访问过则判断页面栈中是否已经有10个页面,有则用wx.redirectTo
,没有则navigateTo
class RouteMap {
constructor(opt={}) {
this.MAX_DEEP = opt.MAX_DEEP || 10
this.IS_AUTO_BACK = opt.IS_AUTO_BACK || true
this.PAGE_STACk = getCurrentPages()
}
_findPageInHistory(path) {
const { PAGE_STACk } = this
let delta = -1
for (let i = 0; i < PAGE_STACk.length; i++) {
if (PAGE_STACk[i].route === path) {
delta = i + 1 // 目标页在栈中的位置
break
}
}
return delta
}
_dataToUrlQuery(data={}) {
let query = '?';
const prop in data) {
if (data.hasOwnProperty(prop)) {
const value = data[prop];
query += `${prop}=${value}&`
}
}
return query.replace(/&$/,'');
}
goPage(opt) {
if (!opt) return new Error('缺少参数')
if (opt && !opt.path) '缺少跳转目标path')
const pageStackLen = PAGE_STACk.length
let { path,data } = opt
let delta = this._findPageInHistory(path)
path = '/' + path.replace(/^\//,0);">'') + this._dataToUrlQuery(data)
if (delta > -1 && this.IS_AUTO_BACK) {
// 如果有目标页已经是被访问过的
const backPage = PAGE_STACk[pageStackLen - delta]
backPage.setData({data});
wx.navigateBack({
delta: pageStackLen - delta
})
} else {
if (pageStackLen < this.MAX_DEEP) {
wx.navigateTo({
url: path
})
} else {
wx.redirectTo({
url: path
})
}
}
}
}
module.exports = RouteMap
但是这样依然有问题,页面传参数变得无法统一,而且明明是前进页面,用户使用的时候很可能看着就是返回了几个页面。
方案二:在小程序页面栈之外维护多一个自己的逻辑栈
- 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逻辑栈(更新时机如上所述),因为这会直接影响中转页的跳转逻辑
这个方案最大的优点在于不用监听页面卸载时对逻辑栈的更新,无需在每个页面里加入更新逻辑栈代码。
本文来自博客园,作者:JackieDYH,转载请注明原文链接:https://www.cnblogs.com/JackieDYH/p/17634599.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现