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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM