从宏观上讲,微信小程序是由一个个组成的 Page 组成。有时我们会遇到一些业务耦合 Page,一个 Page 在某种状态发生变化后,相关状态发生变化 Page 状态需要更新。在小程序中,每个程序, Page 它们都是具有独立功能域的模块 Page 需要一种通信策略。
想象一个业务场景,用户首先进入订单列表页面。然后点击其中一个订单进入订单详细信息页面。当用户在订单详细信息页面上操作订单时,如支付、确认收据等,订单状态将发生变化。此时,需要更新上一级订单列表页面中的订单状态:
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-N3r2ke3t-1633676887031)(006tNc79gy1g58ypytisrj30mx0hiq3n.jpg)]
为了更新订单列表页面的视图层,需要调用此 Page 对象的 setData 方法。以下是三种常用方案:
设置标志位
最简单的方法是在订单详细信息页面成功回调订单的操作时,设置一些标志位置 true,并设置参数(可存在标志位和参数) localStorage 或挂在全局 App 对象下)。然后每次在订单列表页面上 onShow 在生命周期中,根据这些标志位来判断是否更新和更新参数。
这种处理在业务逻辑上相对简单,页面之间的耦合非常小。一旦逻辑复杂,就需要写很多冗余代码,维护成本会很高。
流程图:
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-Wwqb4Bfk-1633676887032)(006tNc79gy1g58yrl4qf3j30nr0l0jt2.jpg)]
使用页面栈获取 Page 对象
如果订单详表页面可以在订单详细信息页面获得 Page 对象可以调用它 setData 方法。小程序提供了一种方法 getCurrentPages,执行它可以得到当前页面堆栈的例子,然后根据页面堆栈的顺序,我们可以得到订单列表页面 Page 对象。
然而,这种做法的缺点是耦合度过高,过于依赖页面进入堆栈的顺序。一旦页面顺序在未来的产品迭代中发生变化,就很难维护。
流程图:
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-7fJJsGeX-1633676887032)(006tNc79gy1g58ysf06w8j30nr0mo402.jpg)]
以上两种方法都存在耦合度高、维护困难的问题,发布/订阅模式可以很好地解耦。让我们先了解一下这种设计模式。
发布/订阅模式(最佳方案)
发布/订阅模式由出版商、多个订阅者和调度中心组成。订阅者首先在调度中心订阅事件并注册相应的回调函数。当出版商发布事件时,调度中心将取出订阅事件的订阅者注册的回调函数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hbo8xVC2-1633676887033)(006tNc79gy1g58ytf9fgaj30es0aj3yv.jpg)]
在发布/订阅模式中,订阅者和出版商不需要关心对方的状态。订阅者只需订阅事件并注册回调,出版商只需发布事件,其余交给调度中心进行调度,以实现解耦。
在 app 跨页通信问题,iOS 端的 Notification Center、安卓端的 EventBus,也是通过这样一种设计模式去解决的,不过微信小程序内部并没有提供这种事件通知机制,所以我们需要手动去实现一个。
首先要实现一个 Event 类,它应该包含收集回调函数的对象,并提供三种基本方法:on(订阅)、 emit(发布)、 off(注销)。
//event.js class Event { on (event, fn, ctx) { if (typeof fn != "function") { console.error('fn must be a function') return } this._stores = this._stores '' {} ;(this._stores[event] = this._stores[event] '' []).push({cb: fn, ctx: ctx}) } emit (event) { this._stores = this._stores '' {} var store = this._stores[event], args if (store) { store = store.slice(0) args = [].slice.call(arguments, 1) for (var i = 0, len = store.length; i < len; i ) { store[i].cb.apply(store[i].ctx, args) } } } off (event, fn) { this._stores = this._stores '' {} // all if (!arguments.length) { this._stores = {} return } // specific event var store = this._stores[event] if (!store) return // remove all handlers if (arguments.length === 1) { delete this._stores[event] return } // remove specific handler var cb for (var i = 0, len = store.length; i < len; i ) { cb = store[i].cb if (cb === fn) { store.splice(i, 1) break } } return } }
具体调用方法
App 它是每个小程序的例子 Page 可以在里面执行 getApp 获得函数。 Event 类实例挂载在 App 每一个都方便 Page 去调用。
// app.js const Event = require('./libs/event') App({ event: new Event() }) 订单列表页在 onLoad 订阅生命周期 “afterPaySuccess” 事件。
//order_list.js var app = getApp() Page({ onLoad: function(){ app.event.on('afterPaySuccess', this.afterPaySuccess, this) }, afterPaySuccess: function(orderId) { }, }) 在订单详细信息页付成功回调 “afterPaySuccess” 同时带上订单 id 参数
//order_detail.js var app = getApp() Page({ raisePayment: function() { app.event.emit('afterPaySuccess', orderId) } }) 所有 Page 的 onUnload 生命周期必须取消以前订阅的事件。取消方法 off 有三种调用姿势,但建议取消目前的调用姿势 Page 所订阅的事件,而不是注销所有的。
var app = getApp() Page({ onUnload: function(){ // remove all app.event.off() // remove all callbacks app.event.off('afterPaySuccess') // remove specific callbacks app.event.off('afterPaySuccess', this.afterPaySuccess) } })