找回密码
 立即注册

QQ登录

只需一步,快速开始

微信小程序跨页面通信解决思路,解决页面耦合问题

[复制链接]
查看: 156|回复: 2
最佳答案
0 

5

主题

5

帖子

130

积分

新人求带

积分
130
宏观上,微信小程序是由一个个 Page 组成的。有时候我们会遇到一些业务存在耦合的 Page,一个 Page 里某个状态改变后,相关 Page 的状态需要进行更新。而在小程序里,每个 Page 都是一个模块,有着独立的作用域,因此 Page 间需要有一种通信策略。

想象一个业务场景,用户首先进入订单列表页。然后点击其中一个订单,进入到订单详情页。当用户在订单详情页对订单进行操作,例如支付、确认收货等时,该订单的状态就会发生改变。此时需要对上一级的订单列表页中该订单的状态进行更新:

event-example.png

要想更新订单列表页的视图层,就需要调用该 Page 对象的 setData 方法。这里为大家列举三种比较常用的方案:

设置标志位

最简单的方法,在订单详情页对订单的操作成功回调中,把一些标志位设置为 true,并设置好参数(标志位和参数可以存在 localStorage 或挂在全局 App 对象下)。然后每次在订单列表页的 onShow 生命周期中,根据这些标志位去判断是否进行更新、更新的参数是什么。

这种处理在业务逻辑比较简单、页面间的耦合度很小时还能凑合,一旦逻辑复杂起来,就需要写很多冗余的葡京赌场网站,并且维护成本会非常高。

流程图:

event-planA.png

利用页面栈获取 Page 对象

如果订单详情页里能拿到订单列表页的 Page 对象,就能去调用它的 setData 方法。小程序提供了一个方法 getCurrentPages,执行它可以得到当前页面栈的实例,然后再根据页面进栈的顺序我们就能拿到订单列表页的 Page 对象。

然而这种做法的缺点还是耦合度太大,过度依赖页面进栈顺序。一旦在以后的产品迭代中页面顺序发生变化,将很难去维护。

流程图:

event-planB.png

上述两种方法都存在着耦合度大、维护困难的问题,而利用发布/订阅模式能很好的实现解耦,下面我们先来了解一下这种设计模式。

发布/订阅模式(最优方案)

发布/订阅模式由一个发布者、多个订阅者以及一个调度中心所组成。订阅者们先在调度中心订阅某一事件并注册相应的回调函数,当某一时刻发布者发布了一个事件,调度中心会取出订阅了该事件的订阅者们所注册的回调函数来执行。

publish.png

在发布/订阅模式中,订阅者和发布者并不需要关心对方的状态,订阅者只管订阅事件并注册回调、发布者只管发布事件,其余一切交给调度中心来调度,从而能实现解耦。

在 app 跨页面通信这个问题上,iOS 端的 Notification Center、安卓端的 EventBus,也是通过这样一种设计模式去解决的,不过微信小程序内部并没有提供这种事件通知机制,所以我们需要手动去实现一个。

我们首先要实现一个 Event 类,它应该含有一个收集回调函数的对象,和提供三个基础方法:on(订阅)、 emit(发布)、 off(注销)。
  1. // event.js
  2. class Event {
  3. /**
  4. * on 方法把订阅者所想要订阅的事件及相应的回调函数记录在 Event 对象的 _cbs 属性中
  5. */
  6. on (event, fn) {
  7. if (typeof fn != "function") {
  8. console.error('fn must be a function')
  9. return
  10. }
  11. this._cbs = this._cbs || {}
  12. ;(this._cbs[event] = this._cbs[event] || []).push(fn)
  13. }
  14. /**
  15. * emit 方法接受一个事件名称参数,在 Event 对象的 _cbs 属性中取出对应的数组,并逐个执行里面的回调函数
  16. */
  17. emit (event) {
  18. this._cbs = this._cbs || {}
  19. var callbacks = this._cbs[event], args
  20. if (callbacks) {
  21. callbacks = callbacks.slice(0)
  22. args = [].slice.call(arguments, 1)
  23. for (var i = 0, len = callbacks.length; i < len; i++) {
  24. callbacks[i].apply(null, args)
  25. }
  26. }
  27. }
  28. /**
  29. * off 方法接受事件名称和当初注册的回调函数作参数,在 Event 对象的 _cbs 属性中删除对应的回调函数。
  30. */
  31. off (event, fn) {
  32. this._cbs = this._cbs || {}
  33. // all
  34. if (!arguments.length) {
  35. this._cbs = {}
  36. return
  37. }
  38. var callbacks = this._cbs[event]
  39. if (!callbacks) return
  40. // remove all handlers
  41. if (arguments.length === 1) {
  42. delete this._cbs[event]
  43. return
  44. }
  45. // remove specific handler
  46. var cb
  47. for (var i = 0, len = callbacks.length; i < len; i++) {
  48. cb = callbacks[i]
  49. if (cb === fn || cb.fn === fn) {
  50. callbacks.splice(i, 1)
  51. break
  52. }
  53. }
  54. return
  55. }
  56. }
复制葡京赌场网站

具体调用方法

App 是小程序的实例,在每个 Page 里都能通过执行 getApp 函数获取到它。我们可以把 Event 类的实例挂载在 App 中,方便每个 Page 去调用。
  1. // app.js
  2. const Event = require('./libs/event')
  3. App({
  4. event: new Event(),
  5. ...
  6. })
复制葡京赌场网站

订单列表页在 onLoad 生命周期中订阅 “afterPaySuccess” 事件。
  1. //order_list.js
  2. var app = getApp()
  3. Page({
  4. onLoad: function(){
  5. app.event.on('afterPaySuccess',this.afterPaySuccess.bind(this))
  6. },
  7. afterPaySuccess: function(orderId) {
  8. ...
  9. },
  10. ...
  11. })
复制葡京赌场网站

在订单详情页支付成功的回调中,发布 “afterPaySuccess” 事件,同时带上订单 id 参数。
  1. //order_detail.js
  2. var app = getApp()
  3. Page({
  4. raisePayment: function() {
  5. ...
  6. app.event.emit('afterPaySuccess', orderId)
  7. },
  8. ...
  9. })
复制葡京赌场网站

所有 Page 的 onUnload 生命周期,必须注销掉之前订阅的事件。注销方法 off 的调用姿势有三种,不过还是建议注销当前 Page 所订阅的事件,而不是注销所有的。
  1. var app = getApp()
  2. Page({
  3. onUnload: function(){
  4. // remove all
  5. app.event.off()
  6. // remove all callbacks
  7. app.event.off('afterPaySuccess')
  8. // remove specific callbacks
  9. app.event.off('afterPaySuccess', this.afterPaySuccess)
  10. },
  11. ...
  12. })
复制葡京赌场网站

到此就结束了吗?还没有,按照我们的订阅、注销写法,在注销指定回调函数的时候,其实是永远注销不了的。

完善off方法

为了让每个回调函数被调用时的 this 都指向对应的 Page 对象,必须在订阅时对回调函数绑定当前的上下文对象。
  1. app.event.on('afterPaySuccess',this.afterPaySuccess.bind(this))
复制葡京赌场网站

相当于
  1. app.event.on('afterPaySuccess', function(){
  2. var args = Array.prototype.slice.call(arguments)
  3. // fn、that分别为闭包起来的回调函数和page对象
  4. return fn.apply(that, args)
  5. })
复制葡京赌场网站

正因为 bind 方法会返回这样一个匿名函数,然后这个匿名函数会被加入到回调数组中。因此我们注销指定回调函数的时候,在回调数组中是找不到它的,也就永远无法注销。

为了保持我们原来的 emit 调用方式,我想过直接把 Function.prototype.bind 改写:
  1. Function.prototype.bind = function(that) {
  2. var fn = this
  3. var cb = function(){
  4. var args = Array.prototype.slice.call(arguments)
  5. return fn.apply(that, args)
  6. }
  7. cb.fn = this
  8. return cb
  9. }
复制葡京赌场网站

然后再稍微修改一下 off 方法里的判断条件
  1. // remove specific callbacks
  2. ...
  3. if (cb === fn || cb.fn === fn) {
  4. callbacks.splice(i, 1)
  5. break
  6. }
  7. ...
复制葡京赌场网站

在浏览器环境这种做法是可行的,但是在小程序侧则是失败的。因为我们定义的这些 function 在小程序里并不是 Function 的实例,那无论我怎样修改 Function 的 prototype 属性,function 并不会继承到。原因是小程序把 Function 给改写了:
  1. //console
  2. Function.toString()
  3. // "function (){if(arguments.length>0&&"return this"===arguments[arguments.length-1])return function(){return e}}"
复制葡京赌场网站

优化方案

在小程序环境中是不能偷懒了,需要把之前的葡京赌场网站改写一下。要把 Page 对象也传给调度中心保存起来,作为回调函数调用时的上下文对象。

葡京赌场网站:
  1. //event.js
  2. class Event {
  3. on (event, fn, ctx) {
  4. if (typeof fn != "function") {
  5. console.error('fn must be a function')
  6. return
  7. }
  8. this._stores = this._stores || {}
  9. ;(this._stores[event] = this._stores[event] || []).push({cb: fn, ctx: ctx})
  10. }
  11. emit (event) {
  12. this._stores = this._stores || {}
  13. var store = this._stores[event], args
  14. if (store) {
  15. store = store.slice(0)
  16. args = [].slice.call(arguments, 1)
  17. for (var i = 0, len = store.length; i < len; i++) {
  18. store[i].cb.apply(store[i].ctx, args)
  19. }
  20. }
  21. }
  22. off (event, fn) {
  23. this._stores = this._stores || {}
  24. // all
  25. if (!arguments.length) {
  26. this._stores = {}
  27. return
  28. }
  29. // specific event
  30. var store = this._stores[event]
  31. if (!store) return
  32. // remove all handlers
  33. if (arguments.length === 1) {
  34. delete this._stores[event]
  35. return
  36. }
  37. // remove specific handler
  38. var cb
  39. for (var i = 0, len = store.length; i < len; i++) {
  40. cb = store[i].cb
  41. if (cb === fn) {
  42. store.splice(i, 1)
  43. break
  44. }
  45. }
  46. return
  47. }
  48. }
复制葡京赌场网站

调用方法也需要改一下,不需要使用 bind 方法了,只需传入 Page 对象:
  1. app.event.on('afterPaySuccess', this.afterPaySuccess, this)
复制葡京赌场网站

原文:https://blog.csdn.net/phj_88/article/details/80994520
回复

使用道具 举报

最佳答案
0 

1

主题

31

帖子

532

积分

略知一二

积分
532
发表于 2018-7-11 22:24:56 | 显示全部楼层
en ,学习了
回复

使用道具 举报

最佳答案
0 

13

主题

823

帖子

6443

积分

S1

刺客

积分
6443
发表于 2018-7-12 07:46:12 | 显示全部楼层
思路很重要
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



www.mainetimberworks.com—微信开发者的分享交流平台,专注微信开发生态。

天津市滨海新区
中新生态城中成大道生态建设公寓9号楼3层301

微信葡京娱乐网址号

市场合作
[email protected]