☰
小程序开发进阶:路由、交互与生命周期机制全解析
2026/10/13 15:45:16 网站建设 项目流程

做微信小程序开发,前面两天我们把环境搭建、项目结构和基础组件跑通了,今天这一篇我们专门啃三块硬骨头:路由、交互与生命周期。这三块是每个页面都必须打交道的东西,但又是很多人只停留在“会用 api”层面的部分。页面跳来跳去突然卡死、传参变成 undefined、返回上一页数据不刷新、明明调用了 setData 却报错页面不存在,这些问题十有八九都出在这三块机制上。

这篇攻略适合写过一段时间小程序、想系统梳理底层逻辑的开发者。我会尽量把“为什么小程序要这么设计”也讲清楚,不只是贴 API。毕竟只有理解了设计意图,遇到坑你才知道往哪查。

1. 路由体系:先把页面栈跑通,再谈各种跳转

1.1 页面栈机制:小程序路由的底层命脉

小程序的路由不像浏览器那样自由,它本质是一个栈结构。你可以想象一摞盘子:你打开一个新页面,就像往盘子上再放一个盘子;你返回上一页,就像取走最上面的盘子。这个栈在官方文档里叫“页面栈”,最多可以放10 层,超过之后调用wx.navigateTo会直接失败。

为什么要用栈而不用“历史记录”?因为手机屏幕小,用户操作路径基本是一条线下钻的,栈结构最符合这种“先进后出”的逻辑。比如首页进列表页,列表页进详情页,详情页进下单页,返回的时候天然就应该一层一层往回退。

我建议你在调试的时候写一段代码打印当前页面栈,这会让你对路由的理解立刻清晰起来:

const pages = getCurrentPages(); console.log(pages.map((page) => page.route));

getCurrentPages()返回的是当前存活页面的实例数组,索引 0 是本页面(因为栈顶在数组末尾,栈底在数组开头)。平时调试跳转层级、排查“为什么返回不到指定页”的时候,这一行代码特别管用。我遇到过不止一次,页面栈里莫名层层叠叠堆了五六个页面,用户点返回要点半天,就是靠这个 API 定位出来的。

这里要说一个很多新手忽略的点:页面栈里的页面实例不会被销毁,除非执行了redirectTo或reLaunch等操作。也就是说,你从 A 页面跳到 B 页面,A 页面的onHide会被触发,但onUnload不会触发,A 页面的数据、定时器、全局变量引用全都还在内存里。这个特性既是优势也是负担,后面讲生命周期的时候还会再提到。

1.2 五种跳转 API 的对比与选型

小程序给开发者提供了五个路由 API,很多人只知道navigateTo和navigateBack,但实际选错 API 是造成页面栈混乱和内存浪费的头号原因。我把它们整理成一张对照表:

API作用对页面栈的影响适用场景
wx.navigateTo跳转到新页面新页面入栈,原页面保留需要返回上级的操作,如列表进详情
wx.redirectTo关闭当前页,跳转到新页面当前页出栈销毁,新页面入栈登录页跳主页、表单填写流程中“下一步”
wx.switchTab跳转到 tabBar 页面关闭所有非 tabBar 页面,但保留 tabBar 页面从子页面切到底部 tab 首页
wx.navigateBack返回上一页或指定层级当前页出栈取消、完成、返回操作
wx.reLaunch关闭所有页面,跳转到任意页面清空整个页面栈全局异常后回到首页、切换角色重建界面

选型的时候记住一句话:能少放一层就少放一层。比如从登录页跳首页,一定用redirectTo,因为登录页已经没有保留意义了;如果登录成功后用户还能通过返回键回到登录页,那体验就很诡异了。再比如表单类的连续流程,每一步都是“下一步”,每一步都应该用redirectTo替换当前页,而不是层层navigateTo。

还有一个细节:switchTab只能跳 tabBar 页面,且它的 url 不能携带参数。如果一定要带参数到 tab 页面,常见的妥协方案是把参数写到globalData或者storage里,然后在 tab 页的onShow里读取。这个方案虽然不算优雅,但确实是当前框架限制下的常规做法。

1.3 超过十层页面栈怎么办

页面栈 10 层的限制是硬约束,但实际项目中真有可能踩到。典型的场景是:用户从首页进入某个商品列表,点进详情,详情里关联推荐又点进另一个详情,再点进分销记录,一层套一层,很快就堆到 10 层。这时候如果再navigateTo,接口会直接报 fail,页面完全没反应,用户还以为自己没点到。

处理这种问题我有两个经验:

第一,详情页对详情页之间跳转必须用redirectTo。详情页本身是同一类型的页面,用户通常不需要一层一层返回到每个中间页。从详情 A 跳详情 B 时,把 A 关掉,栈深度就控制住了,返回时直接从 B 回到列表页,逻辑反而更清晰。

第二,在跳转之前做一个栈层数判断,超过阈值自动降级:

function safeNavigate(url) { const pages = getCurrentPages(); if (pages.length >= 10) { wx.redirectTo({ url }); } else { wx.navigateTo({ url }); } }

这个封装函数是我在自己的项目里一直在用的,成本很低但避免了线上最尴尬的“点击无反应”问题。你也可以在redirectTo失败时兜底给用户一个 toast,而不是直接静默失败。

2. 路由传参与跨页通信:从 query 到事件通道

2.1 常规传参:onLoad 里取 options 的完整链路

小程序页面间传参最基础的方式,就是往 url 上拼参数。跳转方写:

wx.navigateTo({ url: '/pages/detail/detail?id=1001&name=' + encodeURIComponent('张三') });

接收方在页面的onLoad里,通过options拿到参数:

Page({ onLoad(options) { console.log(options.id); // "1001" console.log(decodeURIComponent(options.name)); // "张三" } });

几个很关键的点我得单独拿出来说。

不要直接拿options.name就完事,任何中文字符、特殊字符(&、=、#、空格)都必须先encodeURIComponent编码。我见过太多人因为传了个中文名称,到下一个页面就变成乱码或直接被截断。尤其是商品名称、用户昵称这类字段,不编码必出问题。接收端再用decodeURIComponent解码,一套流程是安全的。

如果参数是一个对象,不能直接拼 url。常规做法是对象先JSON.stringify成字符串,再整体encodeURIComponent,接收端decodeURIComponent+JSON.parse。但要注意 url 参数长度有限制,太长的数据(比如整个购物车对象)就不要走 url 传参了,应该改用storage或者globalData。

传参之后还有个容易忽略的点:url 里带的参数是字符串,不是数字。你传了id=1001,接收端拿到的是字符串"1001",不是数字1001。如果你用它去数据库做查询还没问题,但如果你拿它做加法运算或者类型判断,就等着吃 bug 吧。所以建议接收端习惯性做一次类型转换:

const goodsId = Number(options.id);

2.2 事件通道:跨页数据回传的正确姿势

直接从 A 页面跳转到 B 页面,然后 B 页面选完数据要传回 A 页面,这是电商、表单类小程序最常见的需求。比如你在 A 页面选择城市、选择收货地址、选择优惠券,跳到一个新的选择页面,选完把结果带回来。

很多初学者会把选中的结果存到globalData或storage里,A 页面在onShow里读取。这条路能通,但也有个问题:很容易读到上一次的旧数据,而且如果同时有多个页面共用同一个缓存 key,数据串了就很难排查。

其实小程序官方已经提供了eventChannel事件通道机制,专门解决“跨页通信”的问题。它的用法类似一个轻量级的事件总线,在打开页面的瞬间建立双向通道。

A 页面打开 B 页面并监听 B 传来的事件:

wx.navigateTo({ url: '/pages/select/select', events: { acceptDataFromOpenedPage(data) { console.log('B 页面传回来的数据:', data); } }, success(res) { res.eventChannel.emit('prepareData', { from: 'A' }); } });

B 页面在onLoad中拿到通道,既能接收 A 传来的数据,又能回传结果:

Page({ onLoad() { const eventChannel = this.getOpenerEventChannel(); eventChannel.on('prepareData', (data) => { console.log('A 页面准备好的数据:', data); }); }, onSelectItem(event) { const eventChannel = this.getOpenerEventChannel(); eventChannel.emit('acceptDataFromOpenedPage', { id: event.currentTarget.dataset.id, name: event.currentTarget.dataset.name }); wx.navigateBack(); } });

这种做法最直观的优势是数据流清晰:A 准备什么数据,B 回传什么数据,都在代码里一眼能看到,不像全局变量那样散落各处。而且不会残留脏数据——通道随页面栈一起销毁,下次重新打开时是新通道。

需要注意一个细节:eventChannel.emit触发的事件,如果接收方还没有on监听,数据就会丢掉。所以在 B 页面里,on('prepareData')的注册要尽量早,最好放在onLoad里同步执行。同理,A 页面events里的监听也要尽量在navigateTo配置时就写好,不要等页面打开后再回来补监听。

2.3 页面数据回传:onShow 刷新与缓存清理

事件通道好用,但并不是所有场景都需要它。如果 B 页面不是通过navigateTo打开的(比如某些情况下用户按返回键从系统侧返回),eventChannel就无能为力了,此时你需要用更通用的“返回刷新”方案。

通用的方案是:数据回写 storage,然后在 A 页面的onShow里读取并更新。

// B 页面选中后 wx.setStorageSync('selectedAddress', { id: 'addr_001', name: 'xx路xx号' }); wx.navigateBack(); // A 页面 Page({ onShow() { const selected = wx.getStorageSync('selectedAddress'); if (selected) { this.setData({ address: selected }); // 使用后立即清除,避免下次 onShow 重复读到旧值 wx.removeStorageSync('selectedAddress'); } } });

这里有两个关键习惯:第一,读取完立即可用,不要死循环读取,用 storage 做临时数据通道最怕的就是“读一次不清、下次又读”。第二,优先在onShow里刷新,而不是onLoad,因为onLoad只在页面首次加载时执行一次,从 B 页面返回 A 页面时不会再触发。

我在实际项目里一般把这两种方案混合用:关系型强、需要双向实时通信的场景用eventChannel;只要“结果回填”的场景,直接 storage +onShow,简单可靠,还不用关心通道生命周期。

3. 交互体系:从事件机制到用户反馈

3.1 事件绑定:bind 与 catch 的冒泡差异

微信小程序的交互基础是事件系统。页面里的点击、长按、触摸,本质上都通过事件机制分发到组件上。初学者最容易翻车的地方在于bindtap和catchtap的区别——一个会冒泡,一个不会。

所有以bind开头的事件绑定,都会沿组件树向父级冒泡。比如bindtap触发了子组件的点击事件,如果父级也有bindtap,父级的点击事件同样会被触发。这就是为什么很多人在一个卡片父容器上绑了点击事件,又给里面的按钮绑了点击事件,结果点击按钮时页面莫名其妙跳转了两次。

而catchtap会阻止事件继续冒泡,也就是只在当前组件触发,父级不会再收到这个事件。我把常用的事件关键字列一下:

事件写法是否冒泡使用场景
bindtap/bind:tap是普通点击,父级也监听时可用
catchtap/catch:tap否阻止冒泡,比如卡片内按钮只处理自身点击
mut-bind:tap特殊同节点多个事件响应顺序可控,底层使用较少

事件对象event里的currentTarget和target也是一大误区。简单说,target是你手指真正点到的组件,currentTarget是事件绑定所在的组件。当事件冒泡时,这两者会不同。比如你在view上绑定点击事件,但手指点的是里面的image,那么event.target是 image,event.currentTarget是 view。取值的时候永远用currentTarget的 dataset,因为你的本意是拿绑定组件的数据,而不是被点中的子元素的数据。

还有一个实战中非常常用的技巧:通过><view wx:for="{{goodsList}}" >Page({ onTapGoods(event) { const { id, name } = event.currentTarget.dataset; console.log(id, name); } });

这套写法是列表渲染中点击事件取值的标准姿势,能省掉你给列表每一项都绑一个 handler 的冗余代码。

3.2 交互反馈三件套:loading、toast、modal 的取舍

用户点击任何按钮、发起任何网络请求、完成任何操作,都应该有明确反馈,否则用户会以为页面卡死了。小程序提供的反馈组件看起来很多,但真正高频的其实是这三个。

wx.showLoading用于不可逆的异步操作,比如提交订单、支付确认,它有个特性是会遮挡页面上的点击操作,防止用户在请求期间重复点击。关键参数mask: true可以阻止用户触摸穿透。这里有个常用组合:页面请求数据时,在wx.showLoading的同时禁掉操作按钮,请求完成后wx.hideLoading并恢复。

wx.showToast用于操作完成的轻量提示,比如保存成功、点赞成功、删除成功。默认 1.5 秒后自动消失,但要注意它和showLoading不能同时存在,后调用的一方会把前一个覆盖掉。我的习惯是:网络请求成功之后再调showToast,做请求的时候先showLoading,两者不要混用。

wx.showModal用于需要用户确认的破坏性操作,比如删除某个数据、清空购物车、放弃当前表单填写。它有两个按钮(确认/取消),回调里通过res.confirm判断用户点了哪个按钮。这里有个常见问题:按钮文案默认为“确认/取消”,如果你的产品需要自定义按钮名称,可以通过confirmText和cancelText配置,但注意这两个参数的长度有限制,太多了会被截断。

还有一个被很多人忽略的组件是wx.showActionSheet,它更适合“在多个操作中选择一个”的场景,比如“分享”“收藏”“举报”这类多动作菜单。它配合小程序原生的操作体验,比自绘弹层省很多事。

我特别想提醒一点:不要过度使用弹窗。每个用户操作都弹个 toast、弹个 modal,不仅打断操作节奏,还会让用户产生“烦躁感”。能用按钮状态变化(比如“已关注”变灰)解决的,就不要弹窗。高级的交互是不打扰。

3.3 滚动与触摸事件:下拉刷新、触底加载的正确姿势

移动端的核心交互之一就是滚动,但小程序的滚动事件和浏览器里的滚动事件机制不太一样。页面级滚动通过onPageScroll监听,内容区域滚动则需要在scroll-view上绑定bindscroll。

页面级下拉刷新是所有列表页的标配功能。它不需要你手写触摸事件,只要在页面的 json 配置文件里打开开关:

{ "enablePullDownRefresh": true, "backgroundTextStyle": "dark" }

然后在 Page 里实现onPullDownRefresh回调:

Page({ onPullDownRefresh() { // 重新请求第一页数据 this.fetchList(1).finally(() => { // 数据更新完成后必须手动停止刷新动画 wx.stopPullDownRefresh(); }); } });

这里有个很隐蔽的坑:如果你在onPullDownRefresh里发起了请求,但忘了wx.stopPullDownRefresh(),顶部的刷新动画会一直转个不停,看着就像页面卡住了。正确做法是在数据请求的 finally 里调用,不管成功失败都要把动画停掉。

触底加载是列表页的另一半功能,Page 里实现onReachBottom即可:

Page({ onReachBottom() { if (this.data.loading || this.data.noMore) return; this.loadMore(); } });

注意onReachBottom不能绑定在scroll-view上,只能用于页面级滚动。如果你用的是scroll-view做局部滚动,则要监听bindscrolltolower。

至于手势触摸事件,touchstart、touchmove、touchend在需要自定义手势的场景中很重要,比如右滑删除、滑动切换 tab、抽屉菜单。需要注意的是touchmove可能会引起页面跟随滚动,如果你在做侧滑菜单或滑动手势时,往往需要catchtouchmove来阻止页面滚动,特别是在button组件内套用catchtouchmove时要注意处理。

4. 生命周期:搞清楚页面从生到死的每一秒

4.1 小程序生命周期全景:App、Page、Component 三层

生命周期是最容易“背了概念、排不了错”的知识点。很多学员会把onLoad和onShow混为一谈,其实它们的触发时机完全不同。要搞清楚生命周期,先要分清三套独立的生命周期体系:App 应用级生命周期、Page 页面级生命周期、Component 组件级生命周期。

App 生命周期对应四个回调:

  • onLaunch:小程序冷启动时执行一次,适合做全局初始化,比如读取登录状态、获取系统信息。
  • onShow:小程序从后台进入前台时执行,冷启动和热启动都会触发。
  • onHide:小程序进入后台时执行,比如切到其他 App 或按 Home 键。
  • onError:脚本出错时触发,适合做全局错误监控上报。

这里要区分冷启动和热启动。冷启动是指用户第一次打开小程序,进程被创建,会依次执行onLaunch->onShow。热启动是指小程序已经启动过、只是切换到了后台,再次回到前台时只会执行App.onShow,onLaunch不会再执行。所以任何“每次进入小程序都需要刷新”的逻辑,要放到App.onShow里,而不是onLaunch。

Page 生命周期是开发者打交道最多的:

回调触发时机触发次数
onLoad页面首次加载一次
onShow页面每次显示(包含首次显示和从后台回来)多次
onReady页面首次渲染完成一次
onHide页面被覆盖或小程序进入后台多次
onUnload页面被销毁一次

理解这两套生命周期最关键的一句话是:onLoad是“出生”,onShow是“出现”,onHide是“遮住”,onUnload是“消失”。一个页面从跳转到返回,顺序是:A 的onHide-> B 的onLoad-> B 的onShow-> B 的onReady-> B 返回 -> B 的onUnload-> A 的onShow。

页面被redirectTo销毁时,会触发onUnload,所以如果你的页面里有定时器、全局广播监听,一定要在onUnload里清理干净,否则页面销毁了定时器还在跑,轻则浪费内存,重则操作不存在的页面引发报错。

4.2 生命周期与数据请求:onLoad 和 onShow 的分工

很多新手都会纠结“数据请求到底写在哪个生命周期里”。我的实践原则很明确:

  • 首次加载时的静态数据、路由参数相关的数据,放在onLoad里请求。比如从列表页跳到详情页,详情页通过options拿到id后再请求详情接口,这个动作只做一次,放onLoad最合适。
  • 每次进入页面都需要刷新、或者依赖外部修改的数据,放在onShow里请求。比如购物车页,用户从详情页加购后返回购物车,购物车数量必须刷新;再比如列表页在筛选页选完条件返回,筛选结果要重新请求。

如果这两类数据都写在一个生命周期里,很容易出 bug。我遇到过这样一个案例:某个列表页在onLoad里请求了第一页数据,用户进入筛选页筛选后返回,页面还显示旧数据,因为onLoad不会再次执行。改法是把this.fetchList(1)调到onShow里,每次返回都重新拉取,问题立刻解决。

还有个细节:onReady代表页面首次渲染完成,你可以在这个时机做一些依赖渲染结果的事,比如wx.createSelectorQuery()获取节点坐标、selectComponent获取子组件的实例。如果你在onLoad里就直接调这些方法,大概率会得到 undefined 或者空数据,因为页面还没有真正渲染完。

4.3 组件生命周期与页面生命周期的协同节奏

用了自定义组件之后,生命周期就多了一层。组件的生命周期通过lifetimes定义:

Component({ lifetimes: { created() { // 组件实例刚创建,setData 还不可用 }, attached() { // 组件进入页面节点树,可以 setData 了 }, ready() { // 组件布局完成,可以取节点信息 }, detached() { // 组件被移除,清理工作放这里 } } });

组件生命周期和页面生命周期是有固定顺序的。当页面 A 包含组件 C 时,打开 A 页面的完整时序是:

  1. A 页面onLoad
  2. C 组件created
  3. C 组件attached
  4. A 页面onShow
  5. A 页面onReady
  6. C 组件ready

所以如果你在组件的created里就调用this.setData,此时组件虽然在内存里但还没挂载到页面树,很多数据绑定是不生效的。正确做法是把初始化数据的逻辑放在attached里,把取尺寸、取位置等逻辑放在ready里。

还有一个高频场景:页面怎么调用组件内部的方法?答案是给组件加id或者class,然后在页面onReady后用this.selectComponent('#myComponent')拿到实例。如果你在onLoad里调用selectComponent,一样拿不到,记住这个时序关系你就不会踩坑了。

组件之间通信则是通过properties传入数据、通过triggerEvent向外抛事件。父组件在模板上绑定事件:

<my-component bind:change="onChildChange" />

子组件内部:

// 子组件内某个操作后 this.triggerEvent('change', { value: 123 });

这样父组件就能在onChildChange里收到event.detail.value。这套机制保证了数据流的单向性,也方便后续调试。

5. 高频问题排查与实战避坑记录

5.1 页面跳转后点击无反应:八成是页面栈满了

业务逻辑跑到一半,突然所有navigateTo都没反应,控制台报错提示fail webview count limit exceed,这种情况在复杂的电商流程里非常常见。原因就是页面栈达到了 10 层上限。

我的排查套路是先把getCurrentPages().length打出来,如果确实在 10 层左右,就去检查跳转链路上哪些页面是不需要保留的。详情页套详情页必须用redirectTo,发起支付、确认收货这类操作页也应该尽量保持页面栈精简。如果你做了多处navigateTo跳转但发现很多都只是临时中转,那就要考虑用redirectTo替换。

另外,遇到这个报错也不要只盯着后端逻辑看,先写一个safeNavigate做降级,能跳则跳,不能跳就redirectTo,至少保证页面不“假死”。

5.2 返回上一页后数据不刷新:把请求挪到 onShow 里

这个问题的典型表现是:列表页跳到详情页,在详情页修改了某项数据返回,列表页还是旧数据。不少人会在返回时手动重刷甚至怀疑是缓存原因,其实核心原因就是列表页拉数据的逻辑写在onLoad里,而onLoad只执行一次。

解决方案前面已经说过,凡是“需要每次进入页面都刷新”的,一律放onShow。如果担心onShow里每次都重复请求导致浪费,可以加一个简单的判断:只有从特定页面返回时才刷新,或者用一个时间戳字段判断距上次请求是否超过一定时间。

还有一个小技巧:如果你只是想让某个变量在返回时更新,不一定非要重新请求接口,可以用eventChannel把修改后的值直接传回来。比如编辑页保存后,把新的标题通过事件通道传给列表页,列表页直接setData即可,这样体验更流畅,也省一次网络请求。

5.3 路由参数拿到 undefined 或中文乱码

这是新手最容易懵的问题。页面跳转后,onLoad里的options打印出来全是 undefined,常见有三种原因。

第一种,url 里忘了拼?,参数直接写成了/pages/detail/detail?id=1001这种正确格式应该没问题,但如果参数间用了;或者错误的分隔符就会解析失败。第二种,参数中存在特殊字符,比如&或中文,没做encodeURIComponent,到接收端就断了。第三种,跳转时接收端decodeURIComponent没有配合好,导致显示乱码。

我建议所有参数都统一做一遍编码解码,不要偷懒。对于复杂对象,也统一JSON.stringify + encodeURIComponent,避免参数里带入换行符或中文引号导致 JSON 解析失败。

5.4 交互事件重复触发与穿透

按钮点击重复触发,往往是用户手抖或快速连点,页面没有做防抖。比较稳妥的方案是在请求发起的瞬间把按钮置灰或加上loading状态。比如button自带loading属性,点击后置true,结束再置false。如果不想改按钮结构,可以在逻辑层用一个布尔标记:

Page({ data: { isSubmitting: false }, onSubmit() { if (this.data.isSubmitting) return; this.setData({ isSubmitting: true }); // 异步完成后 this.setData({ isSubmitting: false }); } });

事件穿透问题,比如在一个可点击的卡片子元素里点按钮,结果父卡片的bindtap也被触发了,这是冒泡导致的。解决方法是给按钮的事件绑定改成catchtap。如果卡片的滚动区域出现了元素穿透,多半是touchmove没有被阻止,用catchtouchmove就好。

5.5 生命周期里 setData 报错:注意页面销毁时机

页面已经在点返回瞬间销毁了,但是页面里的异步请求才回来,回调里执行this.setData就会报错“setData 不会触发页面更新,但当前页面可能已销毁”。处理思路很简单:在异步回调前判断页面实例是否还有效。

// 在页面 onUnload 中标记 Page({ onUnload() { this._isUnloaded = true; }, fetchData() { apiGetData((res) => { if (this._isUnloaded) return; this.setData({ data: res.data }); }); } });

类似的场景还有定时器。页面里用了setInterval就一定要在onUnload里clearInterval,否则页面销毁后定时器会继续执行,甚至操作一个已经不存在的 “this”,造成内存泄漏。这也是小程序性能优化里最常见的一类问题。

再补充一个生命周期与setData的细节:onHide阶段页面还没有销毁,只是被覆盖,此时setData是安全的,但页面的渲染优先级会变低,在这个阶段做大量setData反而会造成卡顿,所以除非必要,不要在onHide里做重量级操作。

写在最后

我个人在项目里最受益的一点,就是把“页面栈”和“生命周期”这两张图刻在脑子里,排起错来真的快很多。有时候线上用户反馈页面异常,你根本没法到用户手机上看现场,只能靠日志和分析页面栈位置、触发了哪个生命周期、有没有在onShow里刷新数据来定位问题。你把这些机制吃透了,很多看起来玄学的 bug 其实都能在原理层面找到根因。

最后再分享一个小技巧:开发的时候善用onError做全局异常兜底,在App里统一上报错误信息,带上页面栈数据。这样线上出了 bug,你会第一时间知道“用户卡在哪个页面、页面栈深度是多少、报了什么错”,排查效率会翻倍。路由、交互、生命周期这三块是微信小程序开发的骨架,骨架搭稳了,后面的业务功能写起来会顺手很多。DAY04 我们接着聊网络请求、缓存策略和性能优化,欢迎继续关注。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询