原生自定义事件 (Custom Events)

概述

Custom Events 是浏览器原生的事件机制,可用于实现跨组件、跨框架的通信。通过 CustomEventdispatchEvent API 实现。

特点

  • 原生 API:无需任何依赖
  • 跨框架:React、Vue、Angular 都可使用
  • 跨微前端:不同子应用间通信
  • 浏览器支持好:IE9+ 都支持
  • ⚠️ 仅限同一页面内(不跨标签页)
  • ⚠️ 需要手动管理事件监听器

原生用法

// 发布事件
window.dispatchEvent(
  new CustomEvent('user:login', {
    detail: { userId: 123, name: 'John' }
  })
)

// 订阅事件
window.addEventListener('user:login', (e: CustomEvent) => {
  console.log('User logged in:', e.detail)
})
1
2
3
4
5
6
7
8
9
10
11

核心类

CustomEventBridge

封装原生 API,添加缓存和类型安全。

const bridge = new CustomEventBridge(window, 'app:')

// 发布
bridge.publish('user', { id: 1, name: 'John' })

// 订阅
bridge.subscribe('user', (user) => {
  console.log('User:', user)
})

// 获取缓存
const user = bridge.get('user')

// 取消订阅
bridge.unsubscribe('user', handler)

// 一次性订阅
bridge.once('ready', () => {
  console.log('Ready!')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

API 文档

推荐用法(DOM 事件风格)

import { on, off, dispatch, once, get, clear } from './CustomEventSingleton'
1

dispatch(key, detail)

发布数据,触发 CustomEvent。

dispatch('notification', {
  type: 'success',
  message: '操作成功'
})
1
2
3
4

get(key)

获取缓存的数据。

const notification = get<Notification>('notification')
1

on(key, callback)

订阅事件。如果已有缓存数据,立即触发回调。

on('notification', (data) => {
  showToast(data)
})
1
2
3

off(key, callback)

取消订阅(必须传入相同的函数引用)。

const handler = (data) => console.log(data)
on('key', handler)
// 稍后...
off('key', handler)
1
2
3
4

clear(key)

清除数据并通知订阅者。

clear('notification')
1

once(key, callback)

一次性订阅,触发后自动移除。

once('app:initialized', () => {
  console.log('App ready!')
})
1
2
3

React Hook 示例

function useCustomEvent<T>(key: string): T | undefined {
  const [data, setData] = useState<T | undefined>(() => get<T>(key))

  useEffect(() => {
    on(key, setData)
    return () => off(key, setData)
  }, [key])

  return data
}

// 使用
function NotificationToast() {
  const notification = useCustomEvent<Notification>('notification')

  if (!notification) return null

  return (
    <div className={`toast toast-${notification.type}`}>
      {notification.message}
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

跨框架通信示例

React 组件

// React 发布
function ReactButton() {
  const handleClick = () => {
    dispatch('button:clicked', { from: 'react' })
  }
  return <button onClick={handleClick}>React Button</button>
}
1
2
3
4
5
6
7

Vue 组件

<script setup>
import { onMounted, onUnmounted } from 'vue'
import { on, off } from './CustomEventSingleton'

const handler = (data) => {
  console.log('Received from React:', data)
}

onMounted(() => {
  on('button:clicked', handler)
})

onUnmounted(() => {
  off('button:clicked', handler)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

原生 JavaScript

// 直接使用原生 API 也可以接收
window.addEventListener('shared:button:clicked', (e) => {
  console.log('Received:', e.detail)
})
1
2
3
4

微前端通信

// 主应用
dispatch('micro:route:change', { path: '/dashboard' })

// 子应用 A (React)
on('micro:route:change', (route) => {
  navigate(route.path)
})

// 子应用 B (Vue)
on('micro:route:change', (route) => {
  router.push(route.path)
})
1
2
3
4
5
6
7
8
9
10
11
12

事件名前缀

默认使用 shared: 前缀,避免与其他事件冲突:

// 实际触发的事件名
'shared:user'
'shared:notification'
'shared:cart'
1
2
3
4

自定义前缀:

const bridge = new CustomEventBridge(window, 'myapp:')
// 事件名变为 'myapp:user'
1
2

适用场景

  • 跨框架组件通信(React + Vue + Angular)
  • 微前端子应用间通信
  • 与第三方脚本通信
  • 需要最大兼容性的场景
  • 不想引入任何依赖的场景
上次更新:
(adsbygoogle = window.adsbygoogle || []).push({});