一、为什么需要事件总线?
想象你在开发一个React应用,有这样一个场景:👇
- 页面顶部有个搜索框组件
- 页面底部有个商品列表组件
- 当用户在搜索框输入关键词时,商品列表需要立即更新
如果这两个组件不是父子关系,用props传递数据会非常麻烦!这时候就需要事件总线(Event Bus)这个神奇的工具啦~
📌 通俗解释:事件总线就像一个对讲机,组件A(发送方)对着对讲机说话,组件B(接收方)只要也拿着对讲机(订阅了事件)就能听到,它们不需要知道对方在哪里。
二、认识Mitt:轻量级事件总线工具
Mitt是一个超级迷你的JavaScript库(只有200字节左右!),专门用来实现事件总线功能。它的名字取自"emit"(发射)的谐音。
Mitt的三大优势
- 极简API:只有3个核心方法,5分钟就能学会
- 超轻量:几乎不增加项目体积
- 跨框架通用:React/Vue/Angular都能用
三、快速上手:5分钟安装使用Mitt
步骤1:安装Mitt
打开终端,在项目目录下运行:
npm install mitt
# 或者使用yarn
# yarn add mitt
步骤2:创建事件总线实例
在src目录下新建utils/eventBus.js文件:
// 引入mitt库
import mitt from 'mitt'
// 创建事件总线实例并导出
// 这就像创建了一个专属对讲机频道📻
const eventBus = mitt()
export default eventBus
四、核心API详解
Mitt只有3个核心方法,非常简单!
1. 订阅事件:on()
// 语法:eventBus.on('事件名称', 回调函数)
// 例子:订阅名为'keywordChange'的事件
eventBus.on('keywordChange', (data) => {
console.log('收到新关键词:', data)
// 这里写处理逻辑
})
2. 发布事件:emit()
// 语法:eventBus.emit('事件名称', 要传递的数据)
// 例子:发布'keywordChange'事件,传递搜索关键词
eventBus.emit('keywordChange', '夏季连衣裙')
3. 取消订阅:off()
// 语法:eventBus.off('事件名称', 要移除的回调函数)
// 先定义一个具名函数
const handleKeywordChange = (data) => {
console.log('收到新关键词:', data)
}
// 订阅事件
eventBus.on('keywordChange', handleKeywordChange)
// 取消订阅(通常在组件卸载时)
eventBus.off('keywordChange', handleKeywordChange)
五、实战示例:搜索框与列表组件通信
让我们实现开头说的搜索功能,用两个简单组件演示:
组件1:搜索框(发布者)
import React, { useState } from 'react'
import eventBus from '../utils/eventBus'
const SearchBox = () => {
const [keyword, setKeyword] = useState('')
const handleInputChange = (e) => {
const newKeyword = e.target.value
setKeyword(newKeyword)
// 当输入变化时,发布事件
eventBus.emit('keywordChange', newKeyword)
}
return (
<div style={{ margin: '20px' }}
<input
type="text"
value={keyword}
onChange={handleInputChange}
placeholder="输入搜索关键词..."
style={{ padding: '8px', width: '300px' }}
/>
</div>
)
}
export default SearchBox
组件2:商品列表(订阅者)
import React, { useState, useEffect } from 'react'
import eventBus from '../utils/eventBus'
const ProductList = () => {
const [products, setProducts] = useState([])
const [currentKeyword, setCurrentKeyword] = useState('')
// 组件挂载时订阅事件
useEffect(() => {
// 定义事件处理函数
const handleKeywordChange = (keyword) => {
setCurrentKeyword(keyword)
// 这里可以添加实际的搜索逻辑
console.log('开始搜索:', keyword)
// 模拟搜索结果
setProducts([`${keyword}商品1`, `${keyword}商品2`])
}
// 订阅事件
eventBus.on('keywordChange', handleKeywordChange)
// 组件卸载时取消订阅
return () => {
eventBus.off('keywordChange', handleKeywordChange)
}
}, [])
return (
<div style={{ margin: '20px' }}
<h3>搜索结果:{currentKeyword}</h3>
<ul>
{products.map((product, index) => (
<li key={index}>{product}</li>
))}
</ul>
</div>
)
}
export default ProductList
如何使用这两个组件
import React from 'react'
import SearchBox from './components/SearchBox'
import ProductList from './components/ProductList'
function App() {
return (
<div className="App">
<h1>事件总线演示</h1>
<SearchBox />
<ProductList />
</div>
)
}
export default App
六、初学者必知的注意事项 ⚠
-
内存泄漏问题
- 组件卸载时必须用
off()取消订阅 - 忘记取消会导致组件已经消失但仍能接收事件
- 组件卸载时必须用
-
事件名称规范
- 建议使用统一前缀,如
user:login、cart:update - 避免使用简单字符串如
'change',容易冲突
- 建议使用统一前缀,如
-
数据格式统一
- 建议始终传递对象格式数据,方便扩展:
// 推荐 eventBus.emit('user:login', { id: 1, name: '小明' }) // 不推荐 eventBus.emit('user:login', 1, '小明')
七、Mitt适用场景
适合用Mitt的场景:
- 简单的跨组件通信
- 全局通知(如登录状态变化)
- 非父子组件间的简单交互
不适合的场景:
- 复杂的状态管理(这时候应该用Redux/Vuex)
- 需要追踪状态变化历史
- 大型应用的核心业务逻辑
八、总结
Mitt就像一把小巧的瑞士军刀,虽然简单但在特定场景下非常实用。通过本文你已经学会:
-
事件总线解决了什么问题
-
如何安装和创建Mitt实例
-
三大核心API(on/emit/off)的使用
-
一个完整的跨组件通信示例
-
初学者需要注意的事项