处理微信小程序页面栈限制

132 阅读3分钟

一、前言

前段在工作中碰到这么一个场景,需要在微信小程序内接入百度网盘文件列表进行访问,展示方式是尽可能和网盘那边保持一致,每当打开一个文件夹就等于打开一个新的文件列表页面。

当用户存在深层级嵌套的文件夹,就会涉及到微信小程序页面跳转的限制,也就是页面栈的限制。

每次使用 navigateTo 打开新页面就是页面栈的入栈,使用 navigateBack 返回就是页面栈的出栈。

20250216_01.png

页面栈的限制:

  • 微信小程序的页面栈限制主要是为了防止内存溢出和性能问题。
  • 每个页面使用一个 webview 线程进行渲染,如果页面栈达到 10 层,则会开启 10 个 webview 线程,这会占用较多内存。
  • 如果页面内容过于复杂并且不作限制,可能会导致应用崩溃或运行缓慢。

微信小程序对于页面栈的限制是 10 层。

二、解决思路

针对这个场景,这边有两个方案,各有优缺点。

方案一,swiper 模拟打开页面

让用户在同一个页面进行操作,通过覆盖全屏的 swiper 轮播组件从页面右侧滑出文件夹内容,并实时更新导航标题。

优点:规避小程序最多 10 层的页面栈限制,用户可以不停打开文件夹内容进行访问。

缺点:用户点击手机自带的返回键或者在页面边缘位置右滑触发返回,会直接退出百度网盘文件列表该页面。

20250217_01.jpg

方案二,页面栈满弹窗引导

不停打开新页面访问文件夹内容,直到页面栈快到 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