设想你住在一个叫“都市花园”的小区。每户人家是一个独立的房间——有家具、有窗户、有自己的小空间,就像前端页面里的“组件”。小区里每天都有大量信息要传递:父母给孩子零花钱、孩子向父母汇报作业、邻居之间借东西、物业发停水通知。如果家家户户都靠扯着嗓子喊,小区早就乱套了。于是,小区慢慢形成了一套约定俗成的沟通规矩:直接递、向上喊、找父母转达、看公告栏、听广播、去物业查档案。这些沟通规矩,正是前端组件的通信方式——Props、Emit、Context、EventBus 和状态管理。本文就以这个小区为镜,带你认识这些通信方式的原理、写法与选型,学会像设计师规划小区动线一样,规划组件之间的数据流。

岗位关联

对任何前端开发者来说,组件通信是绕不开的硬核基本功。掌握了它,你才能写出结构清晰、可维护的 Vue/React 应用——这也是 AI 应用前端(如对话面板、模型监控台)能否顺利搭建的关键。

目录
  1. 可视化演示
  2. 一、Props 单向传递:父母把东西递给儿女
  3. 二、Emit 事件回调:孩子喊一声“妈,我饿了”
  4. 三、兄弟组件提升:邻居孩子找父母传话
  5. 四、Context 跨层级共享:小区公告栏
  6. 五、EventBus 全局事件总线:小区广播站
  7. 六、Store 集中状态管理:小区物业中心
  8. 七、通信方式选型:像规划小区动线一样规划数据流
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示四种组件通信模式:Props/Emit父子传参、Context跨层级注入、EventBus事件总线、Store全局状态管理,交互切换理解数据流转方向。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Props/Emit:父子组件直接通信 红色为父组件(Props下发) 粉色为子组件(Emit上报) 橙色为Emit事件回传 Parent -> Props -> Child, Child -> Emit -> Parent Props单向下发数据,Emit向上触发事件,最基础直接的父子通信方式

一、Props 单向传递:父母把东西递给儿女

小区里最自然的沟通,是父母把零花钱、作业本直接塞到孩子手里。孩子可以拿着这些物品开心消费,却不能修改物品上“这是父母给的”这一事实。前端中,props 就是这条“往下递”的通道:父组件把自己拥有的数据通过属性传给子组件,子组件只负责接收和展示,不能反过来改写父组件的数据。

在 React 中,我们这样写:<Child user={user} />,子组件用 function Child({ user }) 接收;在 Vue 中,则用 props: ['user'] 声明接收。值得注意的是,props 是“只读”的,像超市里贴着“样品勿动”的标签——子组件如果试图直接修改,框架会给你一个响亮的警告,因为这破坏了数据的单一归属权。

孩子感受最深的是:零花钱拿到了,但钱袋上写着“保管好,别乱改”。这就是 props 的哲学——单向、显式、清晰。

Parent→propsChild\text{Parent} \xrightarrow{\displaystyle \text{props}} \text{Child}
这条公式代表父组件持有数据,通过 props 这把“递物的手”,把值送到子组件怀里。
  1. 父组件保有数据父组件是数据的所有者,决定数据存什么、何时变,相当于小区里管钱、管作业本的家长。
    data∈Parent\text{data} \in \text{Parent}
  2. 通过属性绑定传递在模板或 JSX 中,把数据写成子组件的某个属性,框架帮你构建一条从父到子的管道。
    Child.props←Parent.data\text{Child}.\text{props} \leftarrow \text{Parent}.\text{data}
  3. 子组件只读消费子组件用收到的 props 渲染页面,但无权直接赋值回父组件,数据流始终单向。
    view=f(props)\text{view} = f(\text{props})

二、Emit 事件回调:孩子喊一声“妈,我饿了”

孩子想告诉父母什么事,不需要自己冲到父母屋子里改账本,而是喊一声“妈,我饿了”。父母听到后自己决定要不要做饭。Emit 就是这个“向上喊话”的机制:子组件在合适的时机发出一个有名字的事件,父组件提前挂好监听器,收到事件后自行更新自己的状态。

Vue 里,子组件用 this.$emit('update', payload) 发出事件,父组件在模板中写 @update="handleUpdate" 监听;React 里虽然没有 emit 这个 API,但它用回调函数实现了同样的效果——父组件把 onUpdate={handleUpdate} 作为 props 传给子组件,子组件在需要时调用 props.onUpdate(payload) 即可。

于是,喊话式沟通保证了“家庭权威”不被孩子挑战:孩子既表达了需求,又把决定权留给了父母。这正是 Emit 与 props 一上一下、彼此配合的精妙之处。

Child→emit(name,payload)Parent\text{Child} \xrightarrow{\displaystyle \text{emit}(\text{name}, \text{payload})} \text{Parent}
emit 的本质是“通知”:子组件不直接改父组件的数据,而是把事件抛出去,就像按门铃,而不是破门而入。
  1. 子组件触发事件在子组件内部调用 emit,并带上事件名和载荷,相当于开口喊话。
    emit:eventName↦payload\text{emit}: \text{eventName} \mapsto \text{payload}
  2. 父组件监听事件父组件提前用 @eventName 或 onEventName 绑定处理函数,像是竖起耳朵等孩子喊。
    Parent⊨on(eventName,handler)\text{Parent} \models \text{on}(\text{eventName}, \text{handler})
  3. 父组件更新自身状态处理函数里写状态更新逻辑,数据仍归父组件所有,子组件只“报案”不“断案”。
    state′=handler(payload)\text{state}' = \text{handler}(\text{payload})

三、兄弟组件提升:邻居孩子找父母传话

小区里两个邻居小孩想一起玩:小明有个魔方,小红想借。小明不会直接把魔方扔进小红家窗户(那太粗暴了),而是告诉各自的父母,由父母商量后把玩具送过去。前端中,两个兄弟组件要通信时,最稳妥的做法是“状态提升”:把共享状态放到最近公共父组件里,兄弟 A 通过 emit 上传,父组件更新状态,再通过 props 下发到兄弟 B。

这个模式在 React 里是官方推荐的常规做法,在 Vue 中也同样常见。它看起来“绕路”,却保证了数据流的可预测性:父组件是唯一的“协调者”,任何变化都有迹可循,不引入额外的状态源。

这就像小区里的“一切纠纷找大人”:虽然多了一个环节,但每个孩子都知道找谁、规则是什么。兄弟通信用状态提升,正是这条朴素规则的体现。

SiblingA→emitParent→propsSiblingB\text{SiblingA} \xrightarrow{\displaystyle \text{emit}} \text{Parent} \xrightarrow{\displaystyle \text{props}} \text{SiblingB}
状态提升的本质是让父组件充当“数据路由器”:兄弟之间不能直接对话,但可以通过父组件中转。
  1. 兄弟 A 上报A 组件通过 emit 把要共享的数据抛给父组件,像是小朋友先求助自己爸妈。
    SiblingA→emit(shareData)Parent\text{SiblingA} \xrightarrow{\text{emit}(\text{shareData})} \text{Parent}
  2. 父组件更新状态父组件在自己的 state 中保存该数据,成为新的数据源,像是父母把“玩具已借出”记在心里。
    Parent←state∪{shareData}\text{Parent} \leftarrow \text{state} \cup \{\text{shareData}\}
  3. 兄弟 B 接收父组件通过 props 把数据传给 B,B 得到只读副本,完成一次经父级的兄弟通信。
    SiblingB←propsParent\text{SiblingB} \xleftarrow{\text{props}} \text{Parent}

四、Context 跨层级共享:小区公告栏

小区物业要通知“晚上 8 点停水”,如果工作人员挨家挨户爬楼敲门,17 层、28 层,逐层传话既慢又容易带错话。于是物业在楼下公告栏贴一张通知,人人都能看见。Context 就是前端世界的“公告栏”:它让处于任何层级的组件都能直接读取共享数据,省去了逐层传 props 的麻烦。

React 中,用 createContext 创建公告栏,<Provider value={...}> 把它挂在组件树顶部,任何后代用 useContext 读取;Vue 中对应的是 provide 和 inject。Context 特别适合存放主题、用户信息、语言包这类“人人都要、层级很深”的数据。

有了公告栏,深层组件不用再抱怨“传值传得手都酸了”。但请注意:公告栏适合低频、全局的数据;如果每个小组件都往公告栏贴东西,小区会变成信息轰炸现场——这背后的取舍,我们在选型一节接着聊。

Provider⇒ value Context⇒ read Consumer\text{Provider} \xRightarrow{\ \displaystyle \text{value}\ } \text{Context} \xRightarrow{\ \displaystyle \text{read}\ } \text{Consumer}
Context 让数据从“逐户传递”变成“集中发布”:数据写一次,想读的组件自己看。
  1. Provider 写入数据顶层组件通过 Provider 把 value 放在 context 对象中,如同物业把通知贴到公告栏。
    Context←Provider(value)\text{Context} \leftarrow \text{Provider}(\text{value})
  2. 任意后代读取任何层级的组件都能调用 useContext / inject 获取数据,无需 parent 中转。
    Consumer←readContext\text{Consumer} \xleftarrow{\text{read}} \text{Context}
  3. 变化自动同步Provider 的 value 变化时,所有读取该 context 的组件自动渲染更新,像公告栏换新通知后被大家看到。
    view→view′whenvalue→value′\text{view} \to \text{view}' \quad \text{when} \quad \text{value} \to \text{value}'

五、EventBus 全局事件总线:小区广播站

有的信息,既不适合从父母手中传,也等不及公告栏更新。比如“小区广场今晚放电影”——物业决定用大喇叭广播一下。广播的妙处在于:任何人都可以听,想听的人打开收音机就行。EventBus 就是前端世界的大喇叭:一个全局的发布/订阅中心,任何组件都能 emit 一条事件,任何组件也都能 subscribe 监听它。

Vue 2 时代,Vue.prototype.$bus = new Vue() 是 EventBus 的经典写法;Vue 3 推荐用 mitt 库,一个只有 200 字节的小广播站。用法很直白:bus.on('order-created', fn) 订阅,bus.emit('order-created', data) 发布;页面销毁时记得 bus.off 退订,否则大喇叭会一直喊给已经离开的人听。

听起来完美,但大喇叭也有麻烦:广播多了,没人知道谁在听谁在说,调试时你将陷入“收音机雪花噪音”。所以 EventBus 适合小范围、低频、临时通信;一旦项目复杂,就该轮到小区物业中心登场了。

A→ emit(e) Bus→ on(e) B\text{A} \xrightarrow{\ \displaystyle \text{emit}(e)\ } \text{Bus} \xrightarrow{\ \displaystyle \text{on}(e)\ } \text{B}
EventBus 适合任意组件之间的临时通信,像是小区的“临时广播”;它不关心消息从哪里来、到哪里去,只负责高效送达。
  1. 订阅事件组件 B 在创建后调用 on,把处理函数注册到总线,像是收音机调频到小区广播。
    Bus←B:on(e,f)\text{Bus} \leftarrow \text{B}: \text{on}(e, f)
  2. 发布事件组件 A 在需要时调用 emit,总线把载荷同步分发给所有订阅者,像是广播员按下麦克风。
    Bus→B:emit(e,payload)\text{Bus} \to \text{B}: \text{emit}(e, \text{payload})
  3. 取消订阅组件销毁前调用 off 移除函数,防止内存泄漏,像是离开小区的人收好收音机。
    Bus∖{f}whenB→destroy\text{Bus} \setminus \{f\} \quad \text{when} \quad \text{B} \to \text{destroy}

六、Store 集中状态管理:小区物业中心

当一个小区大到有上千户,还靠着公告栏和大喇叭来管理信息,显然不够规范。于是成立了物业中心:所有住户的档案、缴费记录、报修工单统一存在物业档案柜里,办什么事都要走物业流程——登记、办理、存档。Store(状态管理库,如 Redux、Pinia、Vuex)就是这个“物业中心”。

在 Redux 中,全局 state 集中存放,组件通过 dispatch 派发 action,reducer 根据 action 计算出新状态,再通知所有订阅的组件更新。Pinia 则用更轻量的方式实现了同样的思想:每个 store 内部有 state、getters、actions。相比 EventBus 的“喊一嗓子”,Store 的方式每一步都有记录,可追踪、可回退,像是物业中心的办事流程有据可查。

有了物业中心,复杂前端项目便可以告别“喊话式”管理。不过,也不是所有状态都该进物业中心——有些邻里间的小事,私聊就够了。最后,我们把整套通信规则整理成一张选型图。

Component→ dispatch(action) Store→ state Component\text{Component} \xrightarrow{\ \displaystyle \text{dispatch}(\text{action})\ } \text{Store} \xrightarrow{\ \displaystyle \text{state}\ } \text{Component}
Store 的核心价值是让状态变化“可预测”:所有修改都走安检门,状态从哪来、改到什么,一目了然。
  1. 组件派发 action用户操作后,组件发出一个描述“发生了什么”的 action,像是住户填了一张报修单。
    action={type:’UPDATE_USER’,payload}\text{action} = \{\text{type}: \text{'UPDATE\_USER'}, \text{payload}\}
  2. Reducer 计算新状态store 内的 reducer 接收旧状态和 action,返回一个新的 state 对象,像是物业审核后更新档案。
    state′=reducer(state,action)\text{state}' = \text{reducer}(\text{state}, \text{action})
  3. 订阅者自动更新store 把新状态广播给订阅它的组件,视图随之刷新,像是档案更新后,办事窗口显示最新信息。
    Component←subscribestore←state′\text{Component} \xleftarrow{\text{subscribe}} \text{store} \gets \text{state}'

七、通信方式选型:像规划小区动线一样规划数据流

小区物业在规划沟通渠道时有一条朴素的准则:能当面说清的,不写公告;能贴公告的,不开广播;必须长期存档的,才进物业中心。前端通信选型也是一样,从“最小必要范围”出发,按下面这张偏好链来决策。

回顾整个小区,每一栋楼、每一户家庭都在恰到好处地使用自己的沟通渠道:低层的用喊、高层的看公告、急事走广播、大事跑物业。前端组件通信的智慧,也正在于此:不是把最强大的工具武装到每一个角落,而是为每一段数据流选择最合适的通道,让系统既清晰又高效。

local≺siblings≺context≺store≺bus\text{local} \prec \text{siblings} \prec \text{context} \prec \text{store} \prec \text{bus}
这条偏好链的意思是:通信范围越大,越要慎重;能用局部方案解决的,绝不用全局方案。
  1. 先局部后全局在选型时从父子通信开始思考,满足需求就不再上升层级,像是先看看对面屋能不能喊到。
    props+emit⇒父子通信\text{props} + \text{emit} \Rightarrow \text{父子通信}
  2. 再考虑提升与共享兄弟或深层组件需要通信时,依次尝试状态提升、Context,每步都评估复杂度,像是先找邻居父母,再看公告栏。
    state-hoisting⇒Context\text{state-hoisting} \Rightarrow \text{Context}
  3. 最后动用全局设施当跨模块、高频率、逻辑复杂时,选择 Store 或 EventBus,并约定数据流规范,像是万不得已才开广播、上档案。
    Store⇒EventBus(全局)\text{Store} \Rightarrow \text{EventBus} \quad (\text{全局})

AI 应用场景

场景 1:AI 对话助手面板

阶段:产品前端交互层

解决的问题:AI 对话面板由 ChatWindow、ModelSelector、TokenCount 等多个组件组成。传统方式中,每个组件各自拉取后端接口,容易产生重复请求和状态不一致;借助组件通信,用户可以在一处切换模型,通过 Store 将模型信息同步到全局,流式输出则用 props 下发到消息列表,Emit 上报“停止生成”事件给控制层,保证交互顺畅。

具体案例:一个类 ChatGPT 应用中,ModelSelector 选择“GPT-4o”,dispatch 到 Pinia store,其他组件自动更新;对话过程中,ChatWindow 通过 EventBus 把“正在生成”状态广播给侧边栏历史记录组件,避免不必要的数据联动。

场景 2:模型训练监控台

阶段:数据可视化与运维监控

解决的问题:在 Dify、Finetune 平台等大模型调度前端中,训练指标图、日志终端、资源监控面板都要实时反映同一份训练数据。传统做法是每个面板隔几秒轮询后端,接口压力大且数据不同步;用 Store 统一管理训练状态后,后端只推送给 store,所有面板订阅同一数据源。

具体案例:某 MLOps 平台的训练详情页,用 Redux Toolkit 管理训练状态:WebSocket 消息到达后统一 dispatch 更新,Loss 曲线、GPU 利用率、日志面板自动刷新,页面间切换也不丢失数据。

场景 3:AI Agent 编排画布

阶段:低代码可视化编排

解决的问题:用户拖拽节点搭建 Agent 流程时,画布节点、右侧属性面板、底部调试输出三块需要实时联动。用 EventBus 做临时选中态同步,能轻量实现三块联动,而不需要为一次点击引入层层 props 传递。

具体案例:Coze 类产品中,用户点击画布节点,canvas 通过 bus.emit('node-select', node) 广播,属性面板与调试面板订阅后展示细节;保存时统一回到 Store 持久化整个 Flow。

Python 演示

python56 行
class EventBus:    """一个极简的发布/订阅中心,类似前端 mitt"""     def __init__(self):        self._listeners = {}     def on(self, event, handler):        self._listeners.setdefault(event, []).append(handler)     def off(self, event, handler=None):        if event not in self._listeners:            return        if handler is None:            self._listeners[event] = []        else:            self._listeners[event] = [                h for h in self._listeners[event] if h is not handler            ]     def emit(self, event, *args, **kwargs):        listeners = list(self._listeners.get(event, []))        for handler in listeners:            handler(*args, **kwargs)  class Component:    """模拟一个前端组件:只会订阅、退订和发布"""     def __init__(self, bus, name):        self.bus = bus        self.name = name        self._messages = []     def subscribe(self, event, handler):        self.bus.on(event, handler)     def unsubscribe(self, event, handler):        self.bus.off(event, handler)     def publish(self, event, data):        print(f"[{self.name}] 发布事件: {event} -> {data}")        self.bus.emit(event, data)     def log(self, data):        self._messages.append(data)        print(f"[{self.name}] 收到消息: {data}")  if __name__ == "__main__":    bus = EventBus()    chat_panel = Component(bus, "聊天面板")    history_list = Component(bus, "历史记录")     history_list.subscribe("new-message", history_list.log)    chat_panel.publish("new-message", "你好,我是 AI 助手")    chat_panel.publish("new-message", "今天想学点什么?")
目的:用 Python 演示 EventBus 的发布/订阅机制,展示组件 A 通过总线把消息传给组件 B,从而理解前端 EventBus 的核心原理。
  1. self._listeners.setdefault(event, []).append(handler)首次遇到事件时创建订阅者列表,再把处理函数加进去。
  2. def off(self, event, handler=None)支持移除特定处理器或清空整个事件的全部订阅,对应前端 bus.off。
  3. history_list.subscribe("new-message", history_list.log)相当于前端 bus.on('new-message', fn)。

常见误区

误区“父组件传下来的 props,子组件直接用 this.props.xxx = xxx 修改不就行了吗?反正都是数据。”

正解Props 是只读的“副本”,子组件直接修改 props 会破坏单向数据流,导致父组件状态和子组件视图不一致。正确做法是:需要修改就通过 emit 通知父组件,由父组件更新数据后重新传下来。类比:孩子拿到零花钱可以自由花,但不能偷偷改掉钱包上写着的“这个月已发 200 元”。

为何常见:初学者刚接触数据绑定,容易把“能读”误解为“能写”,尤其在没有开启严格检查的旧 Vue 2 项目里,该问题更显著。

误区“EventBus 太方便了,任何组件之间都能通信,项目里共享数据都用它就完事了。”

正解EventBus 是全局广播,滥用会让项目陷入“消息满天飞”的失控状态:无人知道某事件从哪发出、有几个订阅者,调试困难;忘记 off 还会导致内存泄漏。它适合低频、临时、松耦合的通信,复杂状态应交给 Store。类比:小区大喇叭适合发停水通知,不适合每天播报每家人的柴米油盐。

为何常见:EventBus 的 API 上手成本极低,写起来像魔法一样高效,容易让人忽略它的维护成本。

误区“有了 Context,所有组件都能读也能写共享数据,跨组件通信就全解决了。”

正解Context 侧重“跨层级读”,不应承担高频、复杂的数据更新职责;它默认会让所有消费组件在 value 变化时重渲染,滥用会引发严重的性能问题。通常用 useMemo/useContextSelector 做优化,或把高频更新的数据交给 Store 管理。类比:公告栏可以贴一份名单,但你不能在公告栏上完成户口的迁移。

为何常见:看到“全局可读”的特性,容易误以为它就是为全局状态而生的,忽略了它是“读取通道”而非“数据中心”。

误区“两个兄弟组件都在同一层,数据传不上去也传不下来,不引入 EventBus 根本没法通信。”

正解最直接且最推荐的方式是通过公共父组件做状态提升:A 用 emit 上报,父组件保存状态,再用 props 下发到 B。相比 EventBus,这种方式的数据流更易追踪。只有当层级很深或实时性要求特殊时才考虑 EventBus/Store。类比:邻居孩子不能直接隔窗喊话时,请父母做中转往往更安全。

为何常见:初学者只看到兄弟组件“平级”,就以为必须有一条平行的通道,忽略了父组件这个天然的“中转站”。

面试题

Q1. Vue 中父组件向子组件传数据最常用的方式是?

单选题 · easy

  • A.通过 props 属性传递← 正确答案
  • B.通过 provide 注入
  • C.通过 store 全局共享
  • D.通过 ref 直接访问

答案:A。props 是 Vue 父子通信的默认入口,最直观;provide 用于跨层级,store 用于全局,ref 更适合直接操作实例。就像父母递东西给孩子最直接不过。

Q2. 子组件想把“用户点击了按钮”告诉父组件,Vue 中最合适的做法是?

单选题 · easy

  • A.修改父组件传来的 props
  • B.调用 this.$emit 触发事件← 正确答案
  • C.直接修改父组件 data 属性
  • D.调用 $nextTick 刷新页面

答案:B。this.$emit 是标准向上通信方式;修改 props 或父组件 data 都违背单向数据流,$nextTick 只在 DOM 更新后执行回调,与此无关。就像孩子只需喊一声,不该自己去翻父母的钱包。

Q3. 在 React 中,兄弟组件需要共享数据,官方推荐的常见做法是?

单选题 · mid

  • A.把状态提升到最近公共父组件里← 正确答案
  • B.用 window.全局变量直接读
  • C.使用 document 元素查找取值
  • D.写一个全局定时器轮询同步

答案:A。状态提升让公共父组件统一管理,数据流清晰可预测;全局变量和 DOM 查询是反模式,定时器轮询更会让数据实时性和性能都失控。就像邻居借玩具找各自父母商量,而不是翻窗直接偷拿。

Q4. React 中 createContext 返回对象上的 Provider 起什么作用?

单选题 · mid

  • A.向所有子孙组件提供共享值← 正确答案
  • B.把组件渲染到指定 DOM 节点
  • C.实现组件自身的状态管理
  • D.代替 props 完成所有数据校验

答案:A。Provider 是 Context 的数据提供端,包裹组件树后,任意后代都能消费 value。B 是 createPortal,C 是 useState,D 是 PropTypes。类比:公告栏立起来后,全楼居民都可以看。

Q5. Vue 3 中推荐用哪个库作为 EventBus 的轻量替代?

单选题 · mid

  • A.mitt← 正确答案
  • B.axios
  • C.vue-router
  • D.pinia

答案:A。mitt 是一个仅约 200 字节的小型发布订阅库,是 Vue 3 社区常用的 EventBus;axios 是请求库,vue-router 是路由库,pinia 是状态管理库。就像广播站换了一套更轻便的设备,而不是买来快递车、地图或档案柜。

Q6. Redux 中,唯一可以改变全局 state 的方式是?

单选题 · hard

  • A.调用 store.dispatch 派发 action← 正确答案
  • B.直接修改 reducer 里的对象
  • C.在组件内用 this.state 赋值
  • D.调用 Context.Provider 传值

答案:A。Redux 要求所有状态变更都通过 dispatch(action) → reducer 计算新状态,保证可预测。直接改 reducer 对象是副作用操作,组件内部 state 属于本地状态,Provider 不修改 store。就像所有业务必须填表走物业流程,不能自行改档案。

Q7. 在 Vue 3 的 <script setup> 中,父组件获取子组件暴露的方法应该用?

单选题 · hard

  • A.defineExpose + ref← 正确答案
  • B.emit + props
  • C.provide + inject
  • D.watch + computed

答案:A。defineExpose 显式暴露子组件的属性或方法,父组件用模板 ref 访问;emit/props 是数据通信,provide/inject 是跨层级依赖注入,watch/computed 与获取子组件方法无关。

Q8. 关于 props 的单向数据流,下列说法正确的有?

多选题 · mid

  • A.子组件不能直接修改 props 值← 正确答案
  • B.子组件只读父组件传入的数据← 正确答案
  • C.props 可以传递函数或事件回调← 正确答案
  • D.子组件可以直接删除 props 属性

答案:ABC。props 是只读的,子组件不能修改也不能删除;可以传递基本类型,也能传函数(在 React 中实现回调)。A、B 描述正确,D 是错误行为。类比:孩子能享用零花钱,但不能撕掉钱包上的小标签。

Q9. EventBus 全局事件总线可能带来哪些问题?

多选题 · hard

  • A.事件不注销造成内存泄漏← 正确答案
  • B.全局命名冲突导致调试困难← 正确答案
  • C.页面复杂度提升后难以追踪数据流← 正确答案
  • D.会显著增加打包后的体积大小

答案:ABC。ABC 是 EventBus 的经典问题——忘记 off 泄漏、事件名冲突、数据流不透明。D 不准确:像 mitt 只有 200 字节左右,体积影响可以忽略。大喇叭虽然方便,但广播多了确实容易让人摸不着头脑。

Q10. 以下哪些场景更适合用 Store(如 Pinia/Redux)而不是 EventBus?

多选题 · hard

  • A.多个页面共享一份用户登录信息← 正确答案
  • B.两个相邻兄弟组件间的临时开关状态
  • C.需要记录操作历史并时间旅行调试← 正确答案
  • D.训练监控面板多模块同步实时指标← 正确答案

答案:ACD。Store 适合全局、高频、可追踪的状态。A、D 需要全局一致性;C 需要 Redux 的时间旅行能力。B 属于局部临时状态,直接用状态提升更合适。就像长期档案必须进物业中心,临时喊话用广播就够了。