移动开发中页面之间通讯是很常见的场景,比如某个页面完成操作后需要通知其他的页面刷新等等之类的。然而微信小程序(后面统称小程序)原生并未提供跨页面通讯的API,所以我们只能自己实现这样一个类似的API。那我们来试想下这个API大概要有一些什么功能?
系统中所有的订阅的消息通过一个全局的字典(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,接下来就是实战了,我们先来一个简单的。
页面A跳转页面B然后在页面B中使用我们的eventbus。
//引入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)
}
})
<navigator url="../fun/fun">{{content}}</navigator>
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
})
}
})
<text bindtap="tap">{{content}}</text>
我们刚进入第一个页面的时候订阅了key为home的消息,接下来看下打印:
接下来我们再看下整个流程效果图:
通过比对代码,发现结果符合我们预期的。
页面A不变,页面B做些许改变
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
})
}
})
<text bindtap="tap">{{content}}</text>
<navigator url="../fun1/fun1" hidden="true">go to thrid page</navigator>
然后增加第三个页面C
var event = require('../../lib/eventbus.js')
var that
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
that = this
},
tap() {
event.pub('home', 'this is new conent')
}
})
<view bindtap="tap">do event </view>
现在key为home的消息在不同页面订阅了两次,看打印先:
可以看到刚进入页面key为home的消息队列为1,后面跳转第二个页面队列为2,退出第二个页面,队列长度又变成1.
接下来我们再看下整个流程效果图:
通过比对代码,效果同样符合预期。
在和pages同级目录下新建component目录,然后在component中新建组件component1
// 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)
}
})
<view>{{content}}</view>
然后在页面三中引用组件component1
{
"usingComponents": {
"component1":"../../component/component1/component1"
}
}
省略...
tap() {
event.pub('component', 'this is new conent')
}
省略...
还有组件间的通讯由于篇幅有限就不做演示了,跟之前提到的方式都大同小异,有兴趣的可以自己试试。
使用姿势如下:
//这里的路径视实际情况而定,按照文中的我的写法的可以按下面的方式引用
var event = require('../../lib/eventbus.js')
event.sub(key, that, function (data) {})
event.pub(key, data)
//页面卸载了记得取消消息订阅防止内存溢出
event.cancel(key, that)
文中已经做了详细的注释和演示,如果还是有不太清楚的小伙伴欢迎留言。
老规矩,喜欢我的文章,欢迎素质三连:点赞,评论,关注,谢谢大家!