评论

微信小程序教你使用eventbus一步一步构建全局跨页面通信系统

教你实现小程序的全局通信

微信小程序提供了页面通信通信解决方案EventChannel,但实际使用中需要在wx.navigateTo跳转页面时使用,且需要在跳转前声明监听的事件,如下图

这是一种页面间的通信,但是局限性过于明显,仅可以在跳转间的页面之间建立通信,A跳转B可以建立通信关系,A不跳转G就不可以建立通信关系,在实际开发中如果某个注册页面的信息想做回显,我们可以使用重新请求、放到storage中、glabalData、eventbus全局通信等,但是肯定不能用navigateTo建立eventbus信道进行传值,从交互层面是完全不可接收的。

这时我们就需要一个全局的eventbus来进行通信了,下面讲解一下微信小程序如何搭建全局eventbus进行通信。

注:eventbus传值,如果没有对引用类型进行深拷贝,那么会将引用传过去导致错误。

首先,我们需要开发页面扩展功能,我们知道每一个页面都是一个Page({}),传入的是一个对象,对象中包含双向绑定的数据、生命周期函数、自定义函数,这里我们需要增加页面的生命周期函数。
原理可以参考这篇文章: 小程序页面(Page)扩展

其中我们需要这5个文件:

其中config.js是小程序全局构造函数App、Page扩展规则配置项,eventBus是eventbus的实现,index是将eventbus扩展的页面上,然后再app.js中引入index文件即可,pageExtends即页面扩展方法,public是初始化eventbus的方法。

使用方法:
A页面声明:


B页面触发:

以下为源码

config.js源码:

/*
 * @Author: 徐强国
 * @Date: 2022-08-15 15:43:32
 * @Description: Page公共方法扩展
 */
const EventBus = require('./eventBus')
let eventBus
// 初始化页面的eventbus,事件用法参照dom2事件
export const initEventBus = (pageObj) => {
    // let eventBus = new EventBus();
    if (!eventBus) {
      eventBus = new EventBus();
    } else {

    }
    pageObj['$on'] = function () {
        let argu = Array.prototype.slice.call(arguments);
        eventBus.on(...argu)
    }
    pageObj['$off'] = function () {
        let argu = Array.prototype.slice.call(arguments);
        eventBus.off(...argu)
    }
    pageObj['$emit'] = function () {
        let argu = Array.prototype.slice.call(arguments);
        eventBus.emit(...argu)
    }
    // 创建页面声明的自定义事件
    let events = pageObj['events'];
    if (Array.isArray(events)) {
        events.forEach((event, index) => {
            if (typeof event === 'string') {
                eventBus.createEvent(event)
            } else {
                console.error(`==请传入String类型的事件名称== index:${index}`, events)
            }
        })
    } else if (typeof events !== 'undefined') {
        console.error('==events字段已被占用,用于声明当前页面需要创建的自定义事件,值为字符串数组== events:', events)
    }
  }

  module.exports = {
    onLoad(options) {
      this.$initPage()
    },

    $initPage() {
      if (!this.$on) {
        initEventBus(this)
      }
    },
  }

eventBus.js源码

/**
 * @authors 徐强国
 * @date 2022-8-8
 * eventBus,订阅/发布
 * 
 */

// 是否是字符串
function isString(str) {
  return typeof str === 'string'
}

// 是否是函数
function isFunction(fn) {
  return typeof fn === 'function'
}

// 消息中心
class MessageHub {
  constructor() {
      this.pubDictionary = {}
  }

  // 创建发布者
  createEvent(name, isGlobal) {
      if (!isString(name)) {
          console.error(`==请传入创建事件的名称 name==`)
          return false
      }
      let _pub = this.pubDictionary[name]
      if (_pub) {
          if (!isGlobal) {
              console.warn(`==${name} 事件已存在==`)
          }
          return _pub
      } else {
          let pub = new Publish(name, this)
          this.pubDictionary[name] = pub
          return pub
      }
  }

  removeEvent(name) {
      if (!isString(name)) {
          console.error(`==请传入删除事件的名称 name==`)
          return false
      }
      delete this.pubDictionary[name]
  }

  on(name, callback, mark) {
      if (!isString(name)) {
          console.error(`==请传入监听事件的名称 name==`)
          return false
      }
      console.log('ononoonon这里的区文体', this.pubDictionary, callback, mark)

      if (!isFunction(callback)) {
          console.error(`==请传入监听事件的回调函数 callback==`)
          return false
      }
      let pub = this.pubDictionary[name]
      if (pub) {
          let watcher = new Watcher(pub.dep, callback, mark)
          pub.dep.addSub(watcher)
      } else {
          console.error(`==尚未创建 ${name} 事件==`)
      }
  }

  off(name, callback) {
      if (!isString(name)) {
          console.error(`==请传入监听事件的名称 name==`)
          return false
      }
      if (!isFunction(callback)) {
          console.error(`==请传入监听事件的回调函数 callback==`)
          return false
      }
      let pub = this.pubDictionary[name]
      pub.dep.removeSub(callback)
  }

  emit(name, val) {
      if (!isString(name)) {
          console.error(`==请传入触发事件的名称 name==`)
          return false
      }
      console.log('这里的区文体emit', this.pubDictionary)
      let pub = this.pubDictionary[name]
      if (pub) {
          pub.refresh(val)
      } else {
          console.warn(`==${name} 事件不存在==`)
      }
  }

  clearEvent() {
      this.pubDictionary = {}
  }
}

//  发布者
class Publish {
  constructor(name, messageHub) {
      this.name = name
      this.messageHub = messageHub
      this.dep = new Dep(this)
  }

  refresh(val) {
      this.dep.notify(val)
  }
}

//  订阅者
class Watcher {
  constructor(dep, run, mark) {
      this.dep = dep
      this.run = run
      this.mark = mark || ''
  }

  update() {
      let val = this.dep.value
      let run = this.run
      run(val)
  }
}

// 依赖收集
class Dep {
  constructor(pub) {
      this.pub = pub
      this.subs = []
  }

  addSub(sub) {
      this.subs.push(sub)
  }

  removeSub(run) {
      let sub = this.subs.filter(item => item.run === run)[0]
      remove(this.subs, sub)
  }

  notify(val) {
      this.value = val
      let subs = this.subs.slice()
      for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update()
      }
  }
}

function remove(arr, el) {
  for (let i = 0; i < arr.length; i++) {
      if (arr[i] === el) {
          arr.splice(i, 1)
          return true
      }
  }
  return false
}

module.exports = MessageHub

pageExtends.js源码

/**
 * @authors 徐强国
 * @date 2022-08-15
 * 小程序全局构造函数App、Page扩展
 */
const { appLiftTimes, pageLiftTimes } = require('./config');

// 判断是否是App的生命周期及原始方法
function isAppLiftTimes (name, fn) {
    if (typeof fn === 'function') {
        return appLiftTimes.indexOf(name) > -1
    }
    return false
}

// 判断是否是Page的生命周期及原始方法
function isPageLiftTimes(name, fn) {
    if (typeof fn === 'function') {
        return pageLiftTimes.indexOf(name) > -1
    }
    return false
}

// 函数混入
function rewriteFn(context, name, fn) {
    if (context[name]) {
        let originFn = context[name];
        context[name] = function (e) {
            let argu = Array.prototype.slice.call(arguments);
            fn.apply(this, argu);
            return originFn.apply(this, argu)
        }
    } else {
        context[name] = fn
    }
}

// 是否是对象
function isObject(obj) {
    return obj !== null && typeof obj === 'object'
}

// 重写App
const originApp = App;
const appExtendsList = [];
App = function (obj) {
    // app拓展方法
    appExtendsList.forEach(item => {
        rewriteFn(obj, item.key, item.value)
    })
    return originApp(obj)
}
const appExtends = function (key, value) {
    if (isAppLiftTimes(key, value)) {
        appExtendsList.push({ key, value })
    } else {
        console.error('==*App暂不支持非生命周期的扩展*==', key)
    }
}

// 重写Page
const originPage = Page;
const pageExtendsList = [];
Page = function (obj) {
    let illegalKeys = Object.keys(obj).filter(key => /^\$+/.test(key));
    if (illegalKeys.length) {
        // throw new Error(`Page中自定义属性禁止以 \$ 开头, ${illegalKeys.join(', ')}`)
        console.error(`Page中自定义属性禁止以 \$ 开头, ${illegalKeys.join(', ')}`)
    }
    // 页面拓展方法
    pageExtendsList.forEach(item => {
        // 非生命周期属性只能拓展一次
        if (isPageLiftTimes(item.key, item.value)) {
            rewriteFn(obj, item.key, item.value)
        } else {
            if (typeof obj[item.key] === 'undefined') {
                obj[item.key] = item.value;
            } else {
                console.error(`Page中已拓展 ${item.key} 属性`, obj[item.key])
            }
        }
    })
    return originPage(obj)
}
const pageExtends = function (key, value) {
    // Page拓展属性,非生命周期的属性必须以 $ 开头
    if (/^\$+/.test(key) || isPageLiftTimes(key, value)) {
        if (isPageLiftTimes(key, value) || !pageExtendsList.filter(item => item.key === key).length) {
            pageExtendsList.push({ key, value })
        } else {
            console.warn(`==*Page中已扩展 ${key} 属性*==`)
        }
    } else {
        console.warn(`==*Page中拓展属性必须以 \$ 开头*==`, `\n key: ${key}`)
    }
}

const AppPlus = {
    appExtends: function (mixinObj, value) {
        if (typeof mixinObj === 'string') {
            appExtends(mixinObj, value)
        } else if (isObject(mixinObj)) {
            Object.keys(mixinObj).forEach(key => {
                appExtends(key, mixinObj[key])
            })
        } else {
            console.warn('==*请传入 对象 或者 key, value*==')
        }
    },
    pageExtends: function (mixinObj, value) {
        if (typeof mixinObj === 'string') {
            pageExtends(mixinObj, value)
        } else if (isObject(mixinObj)) {
            Object.keys(mixinObj).forEach(key => {
                pageExtends(key, mixinObj[key])
            })
        } else {
            console.warn('==*请传入 对象 或者 key, value*==')
        }
    }
}

module.exports = AppPlus

public.js源码

/*
 * @Author: 徐强国
 * @Date: 2022-08-15 15:43:32
 * @Description: Page公共方法扩展
 */
const EventBus = require('./eventBus')
let eventBus
// 初始化页面的eventbus,事件用法参照dom2事件
export const initEventBus = (pageObj) => {
    // let eventBus = new EventBus();
    if (!eventBus) {
      eventBus = new EventBus();
    } else {

    }
    pageObj['$on'] = function () {
        let argu = Array.prototype.slice.call(arguments);
        eventBus.on(...argu)
    }
    pageObj['$off'] = function () {
        let argu = Array.prototype.slice.call(arguments);
        eventBus.off(...argu)
    }
    pageObj['$emit'] = function () {
        let argu = Array.prototype.slice.call(arguments);
        eventBus.emit(...argu)
    }
    // 创建页面声明的自定义事件
    let events = pageObj['events'];
    if (Array.isArray(events)) {
        events.forEach((event, index) => {
            if (typeof event === 'string') {
                eventBus.createEvent(event)
            } else {
                console.error(`==请传入String类型的事件名称== index:${index}`, events)
            }
        })
    } else if (typeof events !== 'undefined') {
        console.error('==events字段已被占用,用于声明当前页面需要创建的自定义事件,值为字符串数组== events:', events)
    }
  }

  module.exports = {
    onLoad(options) {
      this.$initPage()
    },

    $initPage() {
      if (!this.$on) {
        initEventBus(this)
      }
    },
  }

index.js源码

/*
 * @Author: 徐强国
 * @Date: 2022-08-15 15:18:12
 * @Description: 小程序提供扩展App、Page扩展入口
 * 
 * 
 * AppPlus提供拓展App及Page的接口,校验自定义属性命名
 * @param appExtends
 * @parm pageExtends
 * 
 * 传入一个对象,此对象的属性及方法将混入App或者Page实例中
 * 生命周期函数将与自定义的声明周期混合,且先执行,
 * 其他属性只能以$开头,且不可覆盖、混入,应避免名称重复
 */

const AppPlus = require('./pageExtends')
const Public = require('./public')

AppPlus.pageExtends(Public)

代码下载:https://github.com/TobinXu/MiniProgramEventBus/tree/main

最后一次编辑于  2022-08-19  
点赞 5
收藏
评论

2 个评论

  • 元芳
    元芳
    2022-08-24

    出个 DEMO?

    2022-08-24
    赞同
    回复
  • ???
    ???
    2022-08-19

    安卓的eventBus吗😀


    2022-08-19
    赞同
    回复 1
    • 那么那么强国
      那么那么强国
      2023-06-01
      微信小程序的通信系统
      2023-06-01
      回复
登录 后发表内容