微信小程序跨頁面跨組件通訊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
