一、前言
前段在工作中碰到这么一个场景,需要在微信小程序内接入百度网盘文件列表进行访问,展示方式是尽可能和网盘那边保持一致,每当打开一个文件夹就等于打开一个新的文件列表页面。
当用户存在深层级嵌套的文件夹,就会涉及到微信小程序页面跳转的限制,也就是页面栈的限制。
每次使用 navigateTo 打开新页面就是页面栈的入栈,使用 navigateBack 返回就是页面栈的出栈。
页面栈的限制:
- 微信小程序的页面栈限制主要是为了防止内存溢出和性能问题。
- 每个页面使用一个 webview 线程进行渲染,如果页面栈达到 10 层,则会开启 10 个 webview 线程,这会占用较多内存。
- 如果页面内容过于复杂并且不作限制,可能会导致应用崩溃或运行缓慢。
微信小程序对于页面栈的限制是 10 层。
二、解决思路
针对这个场景,这边有两个方案,各有优缺点。
方案一,swiper 模拟打开页面
让用户在同一个页面进行操作,通过覆盖全屏的 swiper 轮播组件从页面右侧滑出文件夹内容,并实时更新导航标题。
优点:规避小程序最多 10 层的页面栈限制,用户可以不停打开文件夹内容进行访问。
缺点:用户点击手机自带的返回键或者在页面边缘位置右滑触发返回,会直接退出百度网盘文件列表该页面。
方案二,页面栈满弹窗引导
不停打开新页面访问文件夹内容,直到页面栈快到 10 层时禁止用户打开下级文件夹,弹窗提示可用全局搜索。
优点:不管怎样返回页面都是符合用户日常操作习惯。
缺点:除去来到百度网盘文件列表前的页面,以及后续需要给全局搜索页面留有余地,用户实际上只能访问五六层的文件夹内容。
三、核心代码
技术栈:uniapp + vue3 + ts
方案一
/src/pages.json
{
"pages": [
{
"path": "pages/index/index",
"style": {
...
"navigationStyle": "custom", // 使用自定义导航,接管返回逻辑
"disableScroll": true // 禁止页面滚动,页面内使用 swiper 包裹 scroll-view
}
}
],
...
}
/src/pages/index/index.vue
<template>
...
<!-- 页面内容 -->
<div class="page">
<swiper
:duration="pageSwitchDurationTime"
:current="state.currentPageIndex"
class="swiper"
@change="actions.onPageSwitch"
>
<swiper-item
v-for="(page, index) in state.pagesData"
:key="index"
class="swiper-item"
>
<scroll-view
scroll-y
show-scrollbar
class="page-content"
@scrolltolower="actions.pageLoadMore(index)"
>
<div
v-for="(i, idx) in page"
:key="idx"
class="item"
@click="actions.openDir(i)"
>
{{ i.name }}
</div>
</scroll-view>
</swiper-item>
</swiper>
</div>
...
</template>
<script setup lang="ts">
...
const pageSwitchDurationTime = 200;
const state = reactive({
...
pagesData: [],
currentPageIndex: 0,
});
// 当轮播组件滑动到上一页,视为返回操作,在滑动动画结束后销毁后面的页面
watch(
() => state.currentPageIndex,
async (newPageIndex, oldPageIndex) => {
if (newPageIndex > oldPageIndex) return;
await sleep(pageSwitchDurationTime);
state.pagesData.pop();
...
},
);
const actions = {
// 用在自定义导航,接管返回逻辑
back() {
if (state.currentPageIndex !== 0) {
state.currentPageIndex--;
return;
}
if (getCurrentPages().length > 1) uni.navigateBack();
},
// 每次打开文件夹,就新增一个全屏的 swiper-item 来模拟新页面
async openDir(item: PageData[0]) {
if (!item.isDir) return;
// 数据加载中
state.pagesData.push([
...
]);
// 确保后续页面数据加载完毕,才去触发 swiper 组件从右侧滑出内容
await sleep(50);
state.currentPageIndex++;
...
},
onPageSwitch(e: any) {
state.currentPageIndex = e.detail.current;
},
...
};
...
</script>
...
方案二
此方案主要通过 getCurrentPages().length
来获取当前页面栈的层级做判断。
四、小结
一开始使用方案一处理微信小程序页面栈限制,但业务觉得这个页面的返回逻辑不符合预期,于是采用了方案二。
百度网盘的微信小程序使用的就是方案二,在页面栈满时弹窗提示用户使用 app,我们则是引导用户使用搜索功能。
完整案例,gitee 仓库 miniprogram-break-page-stack