兄弟组件通信别再层层传 Prop 了 — 一行代码搞定的 Event Bus 反杀方案

问题场景

有这么一个真实页面:左侧是筛选树(Tree),右侧是详情面板(Panel),中间还有一个搜索输入框(SearchBar)。

less 复制代码
┌─────────────┬─────────────────────┐
│  SearchBar  │  SearchBar          │
│  ┌───────┐  │                     │
│  │ 类目树 │  │ 详情面板            │
│  │ A      │  │  商品名称: XXX      │
│  │ ├─ A1  │  │  价格: ¥299        │
│  │ B      │  │  库存: 100         │
│  │ C      │  │  ...               │
│  └───────┘  │                     │
└─────────────┴─────────────────────┘

需求很简单:点击筛选树的节点 → 右侧面板刷新详情 ;搜索框回车 → 树节点高亮 + 面板刷新。

三个跨层级的兄弟/堂兄弟组件,如果老老实实通过父组件 Props 方式:

tsx 复制代码
// App.tsx --- 中间的"转发"代码一大坨
function App() {
  const [selectedNode, setSelectedNode] = useState(null)
  const [searchKeyword, setSearchKeyword] = useState('')

  // 两个回调+两个状态传递,耦合度高
  return (
    <div>
      <SearchBar onSearch={setSearchKeyword} />
      <div className="layout">
        <TreePanel onSelect={setSelectedNode} keyword={searchKeyword} />
        <DetailPanel nodeId={selectedNode} keyword={searchKeyword} />
      </div>
    </div>
  )
}

树一深、状态一多,Props 层层透传 → 修改一个地方要动三个组件 → 改到怀疑人生。

原因分析

问题的本质是:Vue/React 推荐的数据流是单向的 ,但现实中大量存在跨层级、跨兄弟的"事件通知"式通信。

强行用 Props drilling 处理"事件广播"类需求,会导致:

  1. 中间层被迫承载无关 props,组件职责不纯;
  2. 状态一多,父组件变成"上帝对象",维护成本爆炸;
  3. 性能问题:父组件随便一个 state 变化,所有子组件全量 re-render;
  4. 代码可读性差:新同学看代码得从下往上追十层才知道数据从哪来。

Vue 有 Provide/Inject,React 有 Context,但对于这种"非全局、仅局部通知"的场景,用 Context 也是杀鸡用牛刀------Context 一变,所有消费方都得重渲染。

解决方案:轻量 Event Bus(以 React 为例)

不要被"Event Bus 过时了"这种话吓到。在组件间仅需通知、无需共享大量状态的场景,Event Bus 反而是最干净的方案。

tsx 复制代码
// event-bus.ts ------ 一个极小的事件总线
type Handler = (...args: any[]) => void

class EventBus {
  private events = new Map<string, Set<Handler>>()

  on(event: string, handler: Handler) {
    if (!this.events.has(event)) this.events.set(event, new Set())
    this.events.get(event)!.add(handler)
    return () => this.events.get(event)?.delete(handler) // 返回取消订阅
  }

  emit(event: string, ...args: any[]) {
    this.events.get(event)?.forEach(fn => fn(...args))
  }
}

export const bus = new EventBus()

就这么几行,搞定。

tsx 复制代码
// TreePanel.tsx ------ 树节点被点击时
import { bus } from './event-bus'

function TreePanel() {
  const handleSelect = (node: TreeNode) => {
    // ✅ 直接广播事件,不需要父组件转发
    bus.emit('node-selected', node.id)
  }
  // ... render tree
}
tsx 复制代码
// DetailPanel.tsx ------ 监听事件更新自身
import { bus } from './event-bus'
import { useEffect } from 'react'

function DetailPanel() {
  const [data, setData] = useState(null)

  useEffect(() => {
    const unsub = bus.on('node-selected', async (nodeId: string) => {
      const res = await fetch(`/api/detail?id=${nodeId}`)
      setData(await res.json())
    })
    return unsub // 组件卸载自动取消订阅 ✅
  }, [])

  // ... render
}
tsx 复制代码
// SearchBar.tsx ------ 搜索完成后按需广播
import { bus } from './event-bus'

function SearchBar() {
  const handleSearch = (keyword: string) => {
    bus.emit('search', keyword)
  }
  return <Input.Search onSearch={handleSearch} />
}

完美解耦:每个组件只管自己发出/监听什么事件,互相不认识,中间零耦合。

要点总结

场景 推荐方案
父子组件传参 Props(不变)
深层嵌套组件拿数据 React Context / Vue Provide
兄弟/堂兄弟通知 ✅ Event Bus
全局状态跨页面共享 Zustand / Pinia
服务端状态同步 React Query / SWR

Event Bus 的正确打开方式:

  1. 只做通知,不做状态管理 --- Bus 负责喊一嗓子"节点被选了",不要用它存选中的 id(存状态交给 useRef/useState);
  2. 务必取消订阅 --- useEffect return 里 unsub,防止内存泄漏;
  3. 事件名用命名空间 --- tree:node-selected 而不是 selected,避免冲突;
  4. 绝不滥用 --- 一组组件间通知用 Bus 好使;全局用 Zusstand/Pinia。

Vue 版本等价方案(只有一行不一样):

ts 复制代码
// Vue 可以直接用 mitt(2kb)或自己实现
import mitt from 'mitt'
export const emitter = mitt()

然后在 onMounted 里 emitter.on(),onUnmounted 里 emitter.off()。


一句话总结: 别让"Event Bus 过时"的教条堵死了最佳实践。合适场景用合适工具,兄弟组件通知这件事上,10 行 Event Bus 抵得过 50 行 Props drilling + Context Provider 的体操。

相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端