Fork me on GitHub

微信小程序跨页面跨组件通讯eventbus

微信小程序跨页面跨组件通讯eventbus

前言

移动开发中页面之间通讯是很常见的场景,比如某个页面完成操作后需要通知其他的页面刷新等等之类的。然而微信小程序(后面统称小程序)原生并未提供跨页面通讯的API,所以我们只能自己实现这样一个类似的API。那我们来试想下这个API大概要有一些什么功能?

  • 能满足页面之间的通讯
  • 能满足页面和组件(component)之间的通讯
  • 能满足组件(component)之间的通讯
  • 为了能正确响应到自己想要的事件,需要通过一个key来标识每一个事件
  • 而且不同的页面可以使用相同的key来作为事件标识
  • 最后还要使用姿势要简单

实现

首先我们先看下大致的架构图

原理

系统中所有的订阅的消息通过一个全局的字典(Map)来存储,其中的key是事件标识,每个key对应一个数组 (这里用数组而不用单个对象是为了能在不同的页面能用相同的key订阅事件,因为有时候一个页面发布消息需要多个页面响应) ,数组中每个元素是一个对象,其中target表示订阅消息的发起者,callback表示对应发起者的回调函数。然后发布消息的时候直接通过对应的key来拿到消息队列,然后遍历队列发布消息。

实现

由于主要是逻辑实现,没有页面,所以我们新建一个js文件,我这里的目录是和pages同级目录新建lib文件夹,然后lib文件夹新建eventbus.js文件,如下图:

有个全局的字典对象,然后设计三个对外暴露API,分别是:

  • 消息订阅
  • 消息发布
  • 取消订阅
//eventbus.js
var events = new Map()
/**
 * 消息订阅
 * key:消息标识
 * target:消息发起者,用来区分相同key不同的消息
 * callback:回调函数
 */
function sub(key, target, callback) {
}
/**
 * 消息发布
 * key:消息标识
 * data:回调数据
 */
function pub(key, data) {
}
/**
 * 取消订阅
 * key:消息标识
 * target:消息发起者,用来区分相同key不同的消息
 */
function cancel(key,target) {
}

module.exports = {
  sub: sub,
  pub: pub,
  cancel: cancel
}

订阅实现

订阅消息是事件发生的第一环,所以我们首先来写这个API。
按照之前发的架构图来看,订阅消息的时候每个key对应一个消息队列,如果消息队列中有存在target相同的消息,则直接覆盖原来的订阅内容,没有的话则将消息插入队列。

/**
 * 消息订阅
 * key:消息标识
 * target:消息发起者,用来区分相同key不同的消息
 * callback:回调函数
 */
function sub(key, target, callback) {
  //消息对象
  var eobj = {'target':target,'callback':callback}
  //先通过key拿到对应的消息队列
  var value = events.get(key)
  //当前key已存在消息队列说明是不同页面相同的key的消息订阅
  if (Array.isArray(value)){
    //过滤出消息发起者不同的消息,相当于覆盖key和target都一样的消息
    value = value.filter(function(e){
      return e.target != target
    })
    //过滤出的队列重新插入此次订阅的消息
    value.push(eobj)
    events.set(key,value)
  }else {//不是队列表示字典中没有包含当前key的消息,直接插入
    events.set(key,[eobj])
  }
  console.log('function sub ', events)
}

发布实现

订阅消息之后接下来就是发布消息并响应。
这个比较简单,也好理解。通过key来拿到字典(Map)中的消息队列,然后遍历队列逐一进行函数回调即可。

/**
 * 消息发布
 * key:消息标识
 * data:回调数据
 */
function pub(key, data) {
  //通过key拿到消息队列
  var value = events.get(key)
  //如果队列存在则遍历队列,然后调用消息发起者的回调函数,并将data数据进行回调
  if (Array.isArray(value)){
    value.map(function(e){
      var target = e.target
      var callback = e.callback
      callback.call(target, data)
    })
  }
}

取消订阅实现

因为字典中存储的消息队列中包含target对象,这个对象包含的数据较大,如果再订阅消息的页面卸载(回调onupload函数)的时候不取消订阅,容易造成内存溢出。

/**
 * 取消订阅
 * key:消息标识
 * target:消息发起者,用来区分相同key不同的消息
 */
function cancel(key,target) {
  var haskey = events.has(key)
  //是否存在此消息队列
  if(haskey){
    var value = events.get(key)
    if (Array.isArray(value)) {
      //如果队列中只有一条数据直接删除
      if(value.length == 1){
        events.delete(key)
      }else{
        //如果队列中存在多条数据则过滤出和当前取消订阅target不同的消息然后重新设置到key的消息队列中
        value = value.filter(function (e) {
          return e.target != target
        })
        events.set(key, value)
      }
    }
  }
  console.log('function cancel ',events)
}

实战

上面写完了API,接下来就是实战了,我们先来一个简单的。

一个key对应一条消息

页面A跳转页面B然后在页面B中使用我们的eventbus。

  • 页面A.js实现
//引入js文件
var event = require('../../lib/eventbus.js')
var that 
Page({
  /**
   * 页面的初始数据
   */
  data: {
    content: 'go to second page'
  },  

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

    that = this
    event.sub('home', that,function(content){
      that.setData({
        content: content
      })
    })
  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
    event.cancel('home',that)
  }
})

  • 页面A.wxml布局实现
<navigator url="../fun/fun">{{content}}</navigator>

  • 页面B.js实现
var event = require('../../lib/eventbus.js')
var that
Page({
  /**
   * 页面的初始数据
   */
  data: {
    content: 'do event'
  },
  tap() {
    event.pub('home', 'this is new conent')
    wx.navigateBack({
      detla: -1
    })
  }
})

  • 页面B.wxml布局实现
<text bindtap="tap">{{content}}</text>

我们刚进入第一个页面的时候订阅了key为home的消息,接下来看下打印:

接下来我们再看下整个流程效果图:

通过比对代码,发现结果符合我们预期的。

一个key对应多条消息

页面A不变,页面B做些许改变

  • 页面B.js文件

var event = require('../../lib/eventbus.js')
var that
Page({
  /**
   * 页面的初始数据
   */
  data: {
    content: 'do event'
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    that = this
    event.sub('home', that, function (content) {
      that.setData({
        content: content
      })
    })
  },
  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {
    event.cancel('home', that)
  },
  tap() {
    event.pub('home', 'this is new conent')
    wx.navigateBack({
      detla: -1
    })
  }
})

  • 页面B.wxml布局文件
<text bindtap="tap">{{content}}</text>
<navigator url="../fun1/fun1" hidden="true">go to thrid page</navigator>

然后增加第三个页面C

  • 页面C.js文件
var event = require('../../lib/eventbus.js')
var that
Page({
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    that = this
  },
  tap() {
    event.pub('home', 'this is new conent')
  }
})

  • 页面C.wxml布局文件
<view bindtap="tap">do event </view>

现在key为home的消息在不同页面订阅了两次,看打印先:

可以看到刚进入页面key为home的消息队列为1,后面跳转第二个页面队列为2,退出第二个页面,队列长度又变成1.

接下来我们再看下整个流程效果图:

通过比对代码,效果同样符合预期。

页面和组件通讯

在和pages同级目录下新建component目录,然后在component中新建组件component1

  • component1 js文件
// component/component1/component1.js
var that
var event = require('../../lib/eventbus.js')
Component({
  /**
   * 组件的初始数据
   */
  data: {
    content: 'component1'
  },
  // 以下是旧式的定义方式,可以保持对 <2.2.3 版本基础库的兼容
  attached: function () {
    console.log('attached')
    that = this
    event.sub('component', that, function (content) {
      that.setData({
        content: content
      })
    })
  },
  detached: function () {
    console.log('detached')
    event.cancel('component', that)
  }
})

  • component1 布局文件
<view>{{content}}</view>

然后在页面三中引用组件component1

  • 页面三json文件
{
  "usingComponents": {
    "component1":"../../component/component1/component1"
  }
}

  • 页面三js文件tap函数修改

  省略...
  tap() {
    event.pub('component', 'this is new conent')
  }
  省略...

接下来看效果图:

通过比对代码,符合预期效果。

其他通讯场景

还有组件间的通讯由于篇幅有限就不做演示了,跟之前提到的方式都大同小异,有兴趣的可以自己试试。

总结

使用姿势如下:

  • 引入js文件
//这里的路径视实际情况而定,按照文中的我的写法的可以按下面的方式引用
var event = require('../../lib/eventbus.js')

  • 订阅消息
event.sub(key, that, function (data) {})
  • 发布消息
event.pub(key, data)
  • 取消订阅消息
//页面卸载了记得取消消息订阅防止内存溢出
event.cancel(key, that)

原文地址: https://blog.csdn.net/abs625/article/details/106046971

posted @ 2022-02-07 17:28  较瘦  阅读(1179)  评论(0编辑  收藏  举报
知识点文章整理