293 lines
9.8 KiB
JavaScript
293 lines
9.8 KiB
JavaScript
/**
|
||
* Uniapp 事件总线类,用于在应用内进行事件通信
|
||
* 支持同步和异步事件触发,可配置是否继续传播事件
|
||
* 可配置是否在任意监听器抛出错误后立即停止传播
|
||
* 可配置是否在每个监听器执行后调用 defaultAsyncHandler
|
||
* 如果 allowAsyncHandlerRun 为 true,在每个监听器执行后调用 defaultAsyncHandler
|
||
* 如果 allowAsyncHandlerRun 为 false,不调用 defaultAsyncHandler
|
||
* 注意:如果 allowAsyncHandlerRun 为 false,defaultAsyncHandler 不会被调用,也不会等待其 resolve
|
||
*
|
||
* @author ZFSun <lauer3912@gmail.com>
|
||
* @version 1.0.0
|
||
* @createTime 2023-08-10
|
||
*/
|
||
|
||
class EventBus {
|
||
constructor() {
|
||
this.events = new Map()
|
||
this.domBridge = null
|
||
this.platform = this.detectPlatform()
|
||
}
|
||
|
||
// 检测平台
|
||
detectPlatform() {
|
||
// #ifdef H5
|
||
return 'h5'
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
return 'weapp'
|
||
// #endif
|
||
|
||
// #ifdef MP-ALIPAY
|
||
return 'alipay'
|
||
// #endif
|
||
|
||
// #ifdef MP-BAIDU
|
||
return 'baidu'
|
||
// #endif
|
||
|
||
// #ifdef MP-TOUTIAO
|
||
return 'toutiao'
|
||
// #endif
|
||
|
||
// #ifdef MP-QQ
|
||
return 'qq'
|
||
// #endif
|
||
|
||
// #ifdef MP-KUAISHOU
|
||
return 'kuaishou'
|
||
// #endif
|
||
|
||
// #ifdef MP-JD
|
||
return 'jd'
|
||
// #endif
|
||
|
||
// #ifdef MP-360
|
||
return '360'
|
||
// #endif
|
||
|
||
// #ifdef MP-XHS
|
||
return 'xhs'
|
||
// #endif
|
||
|
||
|
||
return 'unknown'
|
||
}
|
||
|
||
// 设置 DOM 桥接
|
||
setDomBridge(bridge) {
|
||
this.domBridge = bridge
|
||
bridge.setEventBus(this)
|
||
}
|
||
|
||
// 监听事件(自动处理平台差异),支持取消
|
||
on(eventName, callback, options = { priority: 0 }) {
|
||
// 如果是 H5 平台且事件名是 DOM 事件,自动创建桥接
|
||
if (this.platform === 'h5' && this.isDomEvent(eventName) && this.domBridge) {
|
||
return this.domBridge.bridgeDomEvent(
|
||
this.getDomEventName(eventName),
|
||
eventName,
|
||
options
|
||
)
|
||
}
|
||
|
||
// 普通 EventBus 监听
|
||
if (!this.events.has(eventName)) {
|
||
this.events.set(eventName, new Set())
|
||
}
|
||
|
||
const handler = { callback, options }
|
||
this.events.get(eventName).add(handler)
|
||
|
||
// 返回取消函数
|
||
return () => this.off(eventName, handler)
|
||
}
|
||
|
||
// 监听多个事件,支持取消
|
||
onMany(eventNames = [], callback, options = { priority: 0 }) {
|
||
// 支持字符串数组或空格分隔的字符串,自动转换为数组
|
||
if (typeof eventNames === 'string') {
|
||
// 支持使用空格分隔事件名
|
||
// 支持使用逗号分隔事件名
|
||
// 下面代码使用正则表达式,自动处理逗号和空格分隔的字符串
|
||
eventNames = eventNames.replace(/\s+/g, ',').split(',')
|
||
}
|
||
const removeFns = eventNames.map(eventName => this.on(eventName, callback, options))
|
||
return () => removeFns.forEach(fn => fn())
|
||
}
|
||
|
||
|
||
// 异步触发事件,支持取消传播,并支持在监听处理后调用异步函数 defaultAsyncHandler
|
||
// 使用方式:await eventBus.emit(eventName, data, defaultAsyncHandler)
|
||
// defaultAsyncHandler 会在每个监听器执行后被调用,签名为 defaultAsyncHandler(event, handler, handlerResult)
|
||
// 如果 defaultAsyncHandler 返回 Promise,会等待其 resolve
|
||
// 如果 allowContinuePropagation 为 false,事件触发后将立即返回,不继续传播
|
||
// 如果 stopOnError 为 true,在任意监听器抛出错误后立即停止传播
|
||
// 如果 allowAsyncHandlerRun 为 true,在每个监听器执行后调用 defaultAsyncHandler
|
||
// 如果 allowAsyncHandlerRun 为 false,不调用 defaultAsyncHandler
|
||
// 注意:如果 allowAsyncHandlerRun 为 false,defaultAsyncHandler 不会被调用,也不会等待其 resolve
|
||
async emit(eventName, data, defaultAsyncHandler, { allowContinuePropagation = true, stopOnError = false, allowAsyncHandlerRun = true } = {}) {
|
||
// 先创建事件对象,方便传入 defaultAsyncHandler
|
||
const event = this.createEvent(eventName, data)
|
||
|
||
// helper to call defaultAsyncHandler only when allowed
|
||
const callDefaultAsyncHandler = async (evt, handler, handlerResult) => {
|
||
if (typeof defaultAsyncHandler !== 'function' || !allowAsyncHandlerRun) return
|
||
try {
|
||
const res = defaultAsyncHandler(evt, handler, handlerResult)
|
||
if (res instanceof Promise) {
|
||
const awaited = await res
|
||
if (awaited === false) evt.preventDefault()
|
||
} else {
|
||
if (res === false) evt.preventDefault()
|
||
}
|
||
} catch (err) {
|
||
console.error(`EventBus defaultAsyncHandler error for ${eventName}:`, err)
|
||
}
|
||
}
|
||
|
||
// 如果是 H5 平台且需要触发 DOM 事件
|
||
if (this.platform === 'h5' && data && data.triggerDomEvent) {
|
||
const domResult = this.domBridge.triggerDomEventFromEventBus(
|
||
this.getDomEventName(eventName),
|
||
data.detail
|
||
)
|
||
|
||
// 调用 defaultAsyncHandler(仅当允许)
|
||
await callDefaultAsyncHandler(event, null, domResult)
|
||
|
||
// 如果不允许继续传播,则直接返回(根据 defaultPrevented 判断结果)
|
||
if (!allowContinuePropagation) {
|
||
return !event.defaultPrevented
|
||
}
|
||
// 否则继续走普通 EventBus 处理(fallthrough)
|
||
}
|
||
|
||
// 普通 EventBus 触发
|
||
const handlers = this.events.get(eventName)
|
||
|
||
// 如果没有监听器,执行 defaultAsyncHandler(如果有),并返回结果
|
||
if (!handlers) {
|
||
await callDefaultAsyncHandler(event, null, true)
|
||
return !event.defaultPrevented
|
||
}
|
||
|
||
// 如果不允许继续传播,则不执行任何监听器,只调用一次 defaultAsyncHandler(若提供),然后返回
|
||
if (!allowContinuePropagation) {
|
||
await callDefaultAsyncHandler(event, null, true)
|
||
return !event.defaultPrevented
|
||
}
|
||
|
||
// 按优先级排序执行
|
||
const sortedHandlers = Array.from(handlers).sort((a, b) => {
|
||
return (b.options?.priority || 0) - (a.options?.priority || 0)
|
||
})
|
||
|
||
// 按优先级顺序执行每个监听器
|
||
for (const handler of sortedHandlers) {
|
||
if (event.defaultPrevented) break
|
||
|
||
try {
|
||
// 支持监听器返回 Promise 或同步值
|
||
const result = handler.callback(event)
|
||
const awaitedResult = result instanceof Promise ? await result : result
|
||
|
||
// 如果回调返回 false,也停止默认行为
|
||
if (awaitedResult === false) {
|
||
event.preventDefault()
|
||
}
|
||
|
||
// 在每个监听器执行后,如果允许并且提供了 defaultAsyncHandler,就调用并等待它
|
||
await callDefaultAsyncHandler(event, handler, awaitedResult)
|
||
} catch (error) {
|
||
console.error(`EventBus ${eventName} error:`, error)
|
||
// 如果全局或该 handler 指定遇错停止传播,则停止
|
||
if (stopOnError || handler.options?.stopOnError) {
|
||
break
|
||
}
|
||
// 否则继续下一个 handler
|
||
}
|
||
|
||
// 一次性监听自动移除
|
||
if (handler.options?.once) {
|
||
this.off(eventName, handler)
|
||
}
|
||
}
|
||
|
||
return !event.defaultPrevented
|
||
}
|
||
|
||
|
||
// 移除监听
|
||
off(eventName, handler) {
|
||
const handlers = this.events.get(eventName)
|
||
if (handlers) {
|
||
handlers.delete(handler)
|
||
if (handlers.size === 0) {
|
||
this.events.delete(eventName)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移除多个事件监听
|
||
offMany(eventNames, handler) {
|
||
eventNames.forEach(eventName => this.off(eventName, handler))
|
||
}
|
||
|
||
// 移除所有监听
|
||
offAll(eventName) {
|
||
this.events.delete(eventName)
|
||
}
|
||
|
||
// 一次性监听
|
||
once(eventName, callback, options = {}) {
|
||
return this.on(eventName, callback, { ...options, once: true })
|
||
}
|
||
|
||
// 前置监听(高优先级)
|
||
preOn(eventName, callback) {
|
||
return this.on(eventName, callback, { priority: 100 })
|
||
}
|
||
|
||
// 后置监听(低优先级)
|
||
postOn(eventName, callback) {
|
||
return this.on(eventName, callback, { priority: -100 })
|
||
}
|
||
|
||
// 判断是否是 DOM 事件
|
||
isDomEvent(eventName) {
|
||
const domEvents = ['click', 'touchstart', 'touchend', 'mousedown', 'mouseup']
|
||
return domEvents.includes(eventName.toLowerCase())
|
||
}
|
||
|
||
// 获取对应的 DOM 事件名
|
||
getDomEventName(eventName) {
|
||
const map = {
|
||
'tap': 'click',
|
||
'click': 'click',
|
||
'touchstart': 'touchstart',
|
||
'touchend': 'touchend'
|
||
}
|
||
return map[eventName] || eventName
|
||
}
|
||
|
||
// 创建可取消的事件对象
|
||
createEvent(eventName, data) {
|
||
const event = {
|
||
type: eventName,
|
||
data,
|
||
timestamp: Date.now(),
|
||
defaultPrevented: false,
|
||
_isPropagationStopped: false
|
||
}
|
||
|
||
// 阻止事件继续传播
|
||
event.preventDefault = function () {
|
||
this.defaultPrevented = true
|
||
}
|
||
|
||
// 停止传播
|
||
event.stopPropagation = function () {
|
||
this._isPropagationStopped = true
|
||
}
|
||
|
||
event.isPropagationStopped = function () {
|
||
return this._isPropagationStopped
|
||
}
|
||
|
||
return event
|
||
}
|
||
}
|
||
|
||
export default new EventBus() |