小程序无限路由方案(proxy 实现)

背景

  • 小程序历史栈最多只支持10层
  • 当小程序业务比较复杂时,就很容易超过10层。
  • 当超过10层后,有的机型是点击无反应,有的机型会出现一些未知错误

解决方法

  • 修改小程序默认导航行为,自行维护 pages
  • 页面层级小于等于10时,导航行为与原生导航行为一致,每次改动都记录到 pages
  • 请求打开第11层及以上时,实际层级每次都是直接将第10层替换为目标页面
  • 返回时,逻辑层级相应回退;若回退后逻辑层级大于等于10,则实际层级将第10层替换为目标页面,否则实际层级回退到相应页面

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
逻辑层级 1 - 2 - ... - 8 - 9 - 10
实际层级 1 - 2 - ... - 8 - 9 - 10

打开

逻辑层级 1 - 2 - ... - 8 - 9 - 10 - 11
实际层级 1 - 2 - ... - 8 - 9 - 11

打开,打开,打开

逻辑层级 1 - 2 - ... - 8 - 9 - 10 - 11 - 12 - 13 - 14
实际层级 1 - 2 - ... - 8 - 9 - 14

返回

逻辑层级 1 - 2 - ... - 8 - 9 - 10 - 11 - 12 - 13
实际层级 1 - 2 - ... - 8 - 9 - 13

返回,返回,返回

逻辑层级 1 - 2 - ... - 8 - 9 - 10
实际层级 1 - 2 - ... - 8 - 9 - 10

返回

逻辑层级 1 - 2 - ... - 8 - 9
实际层级 1 - 2 - ... - 8 - 9

思路

这里采用一种比较特殊的办法,可以不影响业务代码,不需要添加任何其他的代码

我们通过 proxy 的方式,改变全局变量 wx ,将全部路由跳转的方式都进行代理,在这个过程里维护我们自己的 pages 即可。

在 app.js 里

1
2
3
import {router} from 'lib/router'

wx = new Proxy(wx, router)

新建文件 /lib/router.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class  {
constructor() {
this.maxPagesCount = 10
this.pages = []
}

get(target, key, receiver) {
try {
switch (key) {
case 'navigateTo':
case 'redirectTo':
case 'switchTab':
return data => this.changeRouter(data, target, key, receiver)
case 'navigateBack':
return data => this.destroyPage(data, target, key, receiver)
case 'reLaunch':
this.pages = []
return Reflect.get(target, key, receiver)
default:
return Reflect.get(target, key, receiver)
}
} catch (e) {
// 错误时重启 app
return Reflect.get(target, 'reLaunch', receiver)
}
}

changeRouter(data, target, key, receiver) {
const {url} = data
if (this.pages.length < this.maxPagesCount - 1) {
try {
switch (key) {
case 'navigateTo':
case 'switchTab':
this.pushPage(url)
break
case 'redirectTo':
this.popPage()
this.pushPage(url)
break
default:
break
}
Reflect.get(target, key, receiver)(data)
} catch (e) {
Reflect.get(target, 'reLaunch', receiver)(data)
}
} else {
this.pushPage(url)
Reflect.get(target, 'redirectTo', receiver)(data)
}
console.log(this.pages, 'pages');
}

destroyPage(data, target, key, receiver) {
if (this.pages.length < this.maxPagesCount) {
this.popPage()
Reflect.get(target, key, receiver)(data)
} else {
this.popPage()
const url = this.pages.slice(-1)[0]
Reflect.get(target, 'redirectTo', receiver)({url})
}
console.log(this.pages, 'pages');
}

pushPage(url) {
this.pages.push(url)
}

popPage(e) {
return this.pages.pop()
}
}

export const router = new Router()