• 微信小程序跨页面跨组件通讯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

  • 相关阅读:
    散列
    Mac os 使用brew install 安装工具时报错 fatal: not in a git directory Error: Command failed with exit 128: git
    java虚拟机内存溢出
    windows 环境变量 立即生效
    phoenix 建表无法映射hbase已有字段的问题解决
    Phoenix
    Elasticsearch搭建集群步骤:
    分层架构的优缺点
    stateTest
    常用命令
  • 原文地址:https://www.cnblogs.com/jiaoshou/p/15868623.html
Copyright © 2020-2023  润新知