兄弟组件通信别再层层传 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 的体操。

相关推荐
kyriewen1 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒1 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫2 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus2 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude
孟陬3 小时前
Foobar 技术周刊#159:当看不懂代码时,如何高效排障调试。脏页陷阱:一条只读查询,写入数 TB 数据。推测解码:大模型加速利器
前端·人工智能·后端
Zhao-Jichao3 小时前
AI Prompting for Everyone 课程笔记 - P17 图像生成 Imaging Generation
前端
Highcharts.js4 小时前
智能数据分析组件Orbit 正式发布与快速上手体验教程
前端·人工智能·信息可视化·数据分析系统·highcharts·orbit·智能可视化
螺丝钉tina5 小时前
[Playwright ][16]Playwright + TypeScript API 自动化测试实战:API测试
前端·javascript·typescript
小天源5 小时前
智叟云集系统 · 架构与职责分层
服务器·前端·数据库
Highcharts.js7 小时前
React 中原生支持使用 JSX 组件配置自定义表格 Highcharts Grid
前端·javascript·react.js·前端框架·jsx·highcharts·自定义表格