文章

小程序生命周期深度解析

小程序生命周期:应用级(onLaunch/onShow)与页面级(onLoad/onShow/onReady)及页面栈。 讲清各钩子触发时机,掌握后能答出「页面跳转时生命周期如何串联」。

小程序生命周期深度解析

一句话概括

小程序的生命周期有三层嵌套——App → Page → Component,但最容易出 bug 的不是时序复杂,而是开发者搞混了”只执行一次”和”每次显示都执行”这两个类别。

核心知识点

1. 三层生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// App 生命周期
App({
  onLaunch() {},  // 只一次:小程序首次启动
  onShow() {},    // 多次:启动 + 从后台切回 + 从其他小程序切回
  onHide() {},    // 多次:切后台 / 锁屏 / 来电
});

// Page 生命周期(最重要)
Page({
  onLoad(query) {},  // 只一次,带路由参数
  onShow() {},       // 多次,每次出现在用户面前
  onReady() {},      // 只一次,首次渲染完成
  onHide() {},       // 多次,页面被覆盖
  onUnload() {},     // 只一次,navigateBack 销毁
});

2. onLoad vs onShow:最易混淆的一对

1
2
3
4
5
6
7
8
9
10
11
// onLoad:只触发一次,带路由参数,适合"初始化"
// onShow:每次可见都触发,不带参数,适合"刷新/恢复"

// 经典 bug 场景:
// 用户在 A 页面填了表单 → 跳到 B 页面选东西 → 返回 A
// 如果表单初始化写在 onLoad 里 → 返回时触发的 onShow 不会重置表单 ✅
// 如果表单数据在 onShow 里重新请求 → 返回时数据被刷新覆盖 ❌

// 正确做法:
onLoad(query) { this.loadData(query.id); }  // 核心数据只加载一次
onShow() { this.refreshDynamicData(); }     // 只刷新会变化的数据(如库存)

3. 页面栈与生命周期时序

1
2
3
4
5
6
7
// A → navigateTo B
// 时序:B.onLoad → B.onShow → B.onReady → A.onHide
// 关键:B 的三个钩子跑完后,A 才 onHide(不是先隐藏 A 再加载 B)

// B → navigateBack
// 时序:B.onUnload → WebView 销毁 → A.onShow
// 关键:A 的 onLoad 不会再触发!onReady 也不会!

4. Tab 页面的特殊性

1
2
3
4
// Tab 页的 WebView 在启动时就被创建,永远不销毁
// onLoad 只在第一次选中 Tab 时触发
// Tab 切换只触发 onShow/onHide(不会 onUnload)
// 这意味着 Tab 页的 onLoad 可能在 App 启动后很久才触发(用户一直没点那个 Tab)

5. 后台切回:Token 失效的黄金场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
App({
  onShow() {
    const lastActive = wx.getStorageSync('_last_active') || 0;
    if (Date.now() - lastActive > 30 * 60 * 1000) {
      // 超过 30 分钟切回,session 大概率已过期
      this.refreshSession();
    }
    wx.setStorageSync('_last_active', Date.now());
  },
  onHide() {
    // 保存当前状态,防止被杀后台
    wx.setStorageSync('_session_snapshot', this.getStateSnapshot());
  },
});

其实你每天都在用

  • 微信切换账号后的空白:切账号时 App 重新 onLaunch/onShow,之前所有页面的 data 全没了——这就是为什么有些小程序切账号后”数据还在”
  • 从公众号消息打开小程序:App.onShow 会被触发——但 App.onLaunch 不会,因为小程序已经启动了
  • navigateTo 加 redirectTo:电商支付流程标配——支付成功后 redirectTo 结果页(销毁支付页),防止用户返回到支付页重复支付
  • getCurrentPages():这是理解页面栈的万能工具——在 onShow 里调用它,你马上能知道”我从哪个页面过来的”

常见误解(FAQ)

❌ 误区一:”onReady 就是 DOM 准备好了,可以操作节点”

onReady 表示”首次渲染完成”,但不代表所有子组件的 ready 也完成了。如果你需要在 onReady 里通过 selectComponent 获取组件实例并调用其方法,记得那个组件可能还没 ready。安全做法:子组件在 ready 中 triggerEvent(‘componentReady’),父组件收到后再操作。

❌ 误区二:”onHide 里 setData 可以保存用户进度”

onHide 时页面即将被覆盖,setData 的渲染结果用户其实看不到(因为页面已经隐藏了)。保存用户进度应该用 wx.setStorage,而不是依赖 setData。setData 在 onHide 中是浪费渲染资源。

❌ 误区三:”onUnload 里不需要清理,反正页面销毁了”

虽然 WebView 被销毁了,但你在逻辑层的定时器、事件监听、全局存储引用不会自动清理。如果不手动 clearInterval 和 off 事件监听,这些资源会持续占用 JSCore 直到小程序被杀死。onUnload 是清理资源的最后机会。

一句话总结

小程序的 bug 有一半来自生命周期时序——记住”onLoad 只一次,onShow 每次见”这句话,已经能排除 80% 的陷阱。

本文由作者按照 CC BY 4.0 进行授权