评论

深度解析:小程序分包加载导致的“页面重复跳转”及最优解

避免分包加载引起的页面重复打开问题

一、 现象:诡异的“套娃”页面

你是否遇到过这种场景:在小程序中点击一个按钮跳转页面,结果页面跳了两次,甚至多次?用户回退时,需要连续按好几次返回键才能回到首页。

这种情况在分包加载时尤为明显。

二、 诱因:分包加载的“时间差”

为了优化首屏加载速度,我们通常会采用分包策略。例如以下配置:

JSON


{
  "subPackages": [
    { "root": "pages/index/", "pages": ["index"] },
    { "root": "pages/post/", "pages": ["details", "list"] }
  ]
}

当用户从主包跳转到 pages/post/details 时,如果该分包尚未下载,小程序会进入“异步加载”状态。在 Android 设备上,通常会显示 “模块加载中”

问题的关键在于:

在分包下载的过程中,当前页面的逻辑层并未阻塞。如果用户因为网络慢产生焦虑并连续点击handleArticleTap 事件会被多次触发。由于异步任务正在排队,小程序框架会在分包加载完成后,一口气执行掉所有排队的 MapsTo 指令,从而导致页面被重复打开。


三、 踩坑:为什么常规“加锁”会失效?

1. 尝试使用 success 回调

最初的想法可能是:跳转成功后解锁。

JavaScript


handleArticleTap(e) {
  if (this.isNavigating) return;
  this.isNavigating = true;
  
  wx.navigateTo({
    url: '/pages/post/details?pid=1',
    complete: () => {
      // 坑点:这里的 complete 或 success 触发时机极早
      // 往往在分包还没下载完、页面还没真正拉起时就执行了
      this.isNavigating = false; 
    }
  });
}

结论: 失败。wx.navigateTo 的回调代表的是“调用指令成功”,而非“目标页面渲染完成”。

2. 尝试 onShow 生命周期

如你所发现的,利用生命周期锁死跳转状态是一个可行的思路:

JavaScript


// 发起跳转的页面
onShow() {
  this.isNavigating = false; // 页面回来时解锁
},
handleArticleTap(e) {
  if (this.isNavigating) return;
  this.isNavigating = true;
  wx.navigateTo({ url: '...' });
}

结论: 有效,但存在局限性。如果一个页面有 10 个跳转按钮,你需要在每个 Page 里重复写这些逻辑,甚至要在 onHideonUnload 中小心翼翼地维护状态。


四、 进阶:更优雅的全局解决方案

在实际开发中,我们追求的是低侵入性通用性。我们可以通过“函数节流(Throttle)”或“全局路由拦截”来一劳永逸。

方案 A:高阶函数拦截(推荐)

封装一个防重点击的工具函数,避免在每个 Page 里手动维护 lock 变量。

JavaScript


// utils.js
export const throttleNavigate = (fn, gapTime = 1000) => {
  let _lastTime = null;
  return function () {
    let _nowTime = + new Date();
    if (_nowTime - _lastTime > gapTime || !_lastTime) {
      fn.apply(this, arguments); // 距离上次跳转超过1秒才允许再次执行
      _lastTime = _nowTime;
    }
  };
};

// 页面中使用
import { throttleNavigate } from '../../utils/util';

Page({
  handleArticleTap: throttleNavigate(function(e) {
    const { pid } = e.currentTarget.dataset;
    wx.navigateTo({ url: `/pages/post/details?pid=${pid}` });
  })
})

方案 B:重写 wx.navigateTo

如果你想在不改动任何业务代码的前提下解决问题,可以尝试对全局 API 进行增强(Monkey Patch)。

JavaScript


// app.js
const originalNavigateTo = wx.navigateTo;
let isNavigating = false;

wx.navigateTo = function (options) {
  if (isNavigating) return;
  
  isNavigating = true;
  const { complete } = options;
  
  options.complete = function (res) {
    // 延迟 500ms 解锁,给页面跳转预留足够的响应时间
    setTimeout(() => { isNavigating = false; }, 500);
    complete && complete(res);
  };
  
  return originalNavigateTo(options);
};

五、 总结

小程序分包导致的重复跳转,本质上是异步资源加载与同步用户交互之间的矛盾

  • 初级解法: 使用 onShow 手动加锁。
  • 中级解法: 封装 throttle 装饰器函数,控制点击频率。
  • 高级解法: 在框架底层或全局路由封装处进行统一拦截。

在开发过程中,建议始终保持“防重触发”的意识,不仅是为了解决分包加载问题,更是为了提升复杂网络环境下用户的交互体验。


最后一次编辑于  04-07  
点赞 4
收藏
评论

4 个评论

  • Wang
    Wang
    2020-01-11

    其实这种情况不光会出现在分包中,不是分包的页面中也会出现这种情况,只要反应速度慢一点点就可以实现连击现象,不一定是两次,也许是无限次,只要点击速度够快。

    2020-01-11
    赞同 2
    回复 1
    • AndroidWalletSDKDemo
      AndroidWalletSDKDemo
      发表于移动端
      2020-11-13
      熊云方15220351875
      2020-11-13
      1
      回复
  • 罐梨脆紫柿
    罐梨脆紫柿
    2020-01-07

    这一万多个关注咋刷的

    2020-01-07
    赞同 1
    回复 7
    • Hasaki
      Hasaki
      2020-01-07
      一个一个关注的呀
      2020-01-07
      1
      回复
    • 罐梨脆紫柿
      罐梨脆紫柿
      2020-01-07回复Hasaki
      哪里找到的这么多人,
      2020-01-07
      1
      回复
    • Hasaki
      Hasaki
      2020-01-07
      爬的,,我可是被封过号的人...
      2020-01-07
      1
      回复
    • 罐梨脆紫柿
      罐梨脆紫柿
      2020-01-07回复Hasaki
      这么厉害。
      2020-01-07
      1
      回复
    • 王浩Hanks🇨🇳
      王浩Hanks🇨🇳
      2020-01-07回复Hasaki
      爬半天,1w多个人,也不关注我  T_T
      2020-01-07
      1
      回复
    查看更多(2)
  • li  jian🏁
    li jian🏁
    发表于移动端
    2022-01-13
    丝g Hxhcz .c
    2022-01-13
    赞同
    回复
  • 🌼🌼🌼
    🌼🌼🌼
    发表于移动端
    2021-03-15
    打开问题
    2021-03-15
    赞同
    回复
登录 后发表内容