# 【方案】-小程序跨页面通信解决方案

# 场景叙述

在我们日常小程序开发中会遇到这些类型的需求如:

  • 点击授权按钮,弹出授权的一系列逻辑,或者跳转到授权的页面进行一系列操作,然后返回到最初的页面的时候希望在没有请求接口的情况下授权状态变为已经授权

  • 在房源列表点击某一项进入房源详情页,然后进行了一大堆操作,如关注、收藏等,然后希望点击返回键返回列表页的时候对应的收藏状态、关注状态进行实时的修改

# 方案一 - 设置标志位

以场景二房源为例子,我们在订单详情进行关注、收藏等操作之后,将一些标志位如hasFollowid等挂载到全局如localstorge,然后再房源列表页的onShow钩子里面判断这些标志位,如果标志位存在则进行对应的逻辑修改

WARNING

缺陷: 业务逻辑复杂起来的时候代码很冗余,维护成本很高,对于新人刚接触代码时候看到一大堆挂载全局的逻辑很不友好

# 方案二 - 利用页面栈获取 Page 对象

我们知道,小程序间的页面跳转,其实原理就是进栈和出栈。小程序提供了一个api - getCurrentPages,通过这个api我们可以获取到小程序当前的调用栈,然后根据这个调用栈可以获取到对应页面的page对象。

假设我们现在在房源详情页,然后我们点击关注成功的回调里面调用getCurrentPages获取到房源列表页的page对象,然后调用这个page对象的setData方法来进行对应的页面修改

WARNING

缺陷: 如果对于页面进入离开的顺序没有发生变化的需求,这个方案可以满足,但是一旦产品需求中页面的顺序发生变化,这个方案就显得有点无力了

# 方案三 - 发布订阅

这个也是目前我们使用在项目上的最终方案

熟悉发布订阅模式的同学应该知道这个模式大致有发布者,订阅者,调度中心组成。订阅者们先在调度中心订阅某一事件并注册相应的回调函数,当某一时刻发布者发布了一个事件,调度中心会取出订阅了该事件的订阅者们所注册的回调函数来执行。

TIP

发布订阅模式在很多框架中都用到了,如node的events、redux中的订阅、vue的双向数据绑定等等等

实现一个最简单发布订阅的event类,实际业务代码会在这个基础上面进行扩张如打印、上报等




 
























































class Event {

  on(event, fn, ctx) {
    if (typeof fn != "function") {
      console.error('事件注册错误')
      return
    }

    this._events = this._events || {};

    (this._events[event] = this._events[event] || []).push({ cb: fn, ctx: ctx })//将事件和上下文存储起来
  }

  emit(event) {
    this._events = this._events || {}
    var store = this._events[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._events = this._events || {}

    // 如果不传参数,将存储全部清空
    if (!arguments.length) {
      this._events = {}
      return
    }

    // 匹配不到事件,退出
    var store = this._events[event]
    if (!store) return

    // 如果只有一个参数event,将对应存储的全部事件删除
    if (arguments.length === 1) {
      delete this._events[event]
      return
    }

    // 删除某一个事件
    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
  }
}

module.exports = Event

那我们该如何使用这个类来解决问题呢?

首先我们知道App是小程序的实例,我们可以在每个页面通过getApp()获取到他,那么我们可以把我们的event类挂载到App实例上面




 




// App.js
const Event = require('./libs/event')

App({
    event: new Event(),
    ...
})

然后我们在每个页面可以通过如下方式获取到我们的事件类,并且调用对应的方法




 








//page - houses.js 房源列表页

const app = getApp()

Page({
    onLoad: function() {
        app.event.on('houseFollow', (type) => {
            // 执行对应的逻辑
        })
    }
})

我们在房源列表页通过app.event.on()的方式订阅一个事件,然后就可以在房源详情页通过app.event.emit()发布这个事件,在我们的房源列表页就可以接受到订阅信息,执行回调函数了




 







//page - house-detail.js 房源详情页

const app = getApp()

Page({
    follow() { // 关注事件
        ...
        app.event.emit('houseFollow', 1) // 还可以带参数发布
    }
})

最后,我们只需要在页面中注协掉这个订阅事件就好了, 我们这里提供了很多种注销手段,不过优先应该考虑注销当前页面的事件




 




Page({
    onUnload: function(){
        ...
        app.event.off()
    },
    ...
})

# 参考文章

如何在微信小程序里面实现跨页面通信?