第 24 题:Vue3 的组件通信方式(props / emit / v-model / provide-inject / expose / eventBus / store)
🎯 一、Vue3 组件通信方式总结表(可背)
| 通信方式 | 方向 | 适用场景 | 是否响应式 |
|---|---|---|---|
| props | 父 → 子 | 传值、配置 | ✅ |
| emit | 子 → 父 | 通知事件、返回数据 | N/A(事件) |
| v-model | 双向(父 ↔ 子) | 表单 / 双向绑定组件 | ✅ |
| provide / inject | 祖先 → 后代 | 跨层级通信、通用组件 | 默认非响应式(可用 ref) |
| expose | 子 → 父(实例 API) | 暴露内部方法供父调用 | N/A |
| EventBus | 任意 ↔ 任意 | 小型项目的全局事件 | 依赖实现 |
| Pinia / Vuex | 全局共享 | 大型项目、复杂业务 | ✅ 强响应式 |
🎯 二、各通信方式详解
1️⃣ Props:父 → 子(最核心通信方式)
示例
ini
<Child :msg="parentMsg" />
子组件
scss
defineProps({
msg: String
})
适用场景
- 父组件传值给子组件
- 表单项、配置项、初始化数据
原理
编译模板时,props 变成组件的属性,最终映射到组件实例上。
Vue3 对 props 做 shallowReadonly,防止子组件修改。
2️⃣ Emit:子 → 父
子组件:
scss
const emit = defineEmits(['update'])
emit('update', newVal)
父组件:
ini
<Child @update="handleUpdate" />
适合:事件通知、按钮点击回调等。
3️⃣ v-model(双向通信)
Vue3 支持多个 v-model:
父:
ini
<Child v-model:title="name" />
子:
ini
const props = defineProps(['title'])
const emit = defineEmits(['update:title'])
emit('update:title', newVal)
本质:props + emit 的语法糖
4️⃣ provide / inject(跨层级通信)
祖先组件:
csharp
provide('theme', ref('dark'))
后代组件:
ini
const theme = inject('theme')
特点
- 跨层级通信(不用 props 逐层传递)
- 默认非响应式,如需响应式必须提供 ref/reactive
原理(常考)
provide 在组件创建时写入 "依赖注入表",
inject 在后代组件创建时从原型链查找依赖。
5️⃣ expose(子组件暴露方法给父组件)
Vue3 新增的,非常常用!
子组件:
scss
const count = ref(0)
function reset() {
count.value = 0
}
defineExpose({
reset
})
父组件:
ini
<Child ref="childRef" />
childRef.value.reset()
适用:表单校验、重置、打开弹窗等。
Vue3 默认 setup 中的方法不会暴露给父组件,必须用 expose。
6️⃣ EventBus(小项目用,全局事件通信)
Vue3 没有直接提供,可以自己用 mitt:
javascript
import mitt from 'mitt'
export const bus = mitt()
csharp
bus.emit('login', user)
bus.on('login', handler)
适合:
- 非父子组件通信
- 简单的全局事件系统
7️⃣ Pinia(全局状态管理)
ini
const store = useUserStore()
store.name = 'jack'
最适合:
- 大型项目
- 全局共享数据
- 多组件同步更新
Vue3 官方推荐 Pinia 代替 Vuex。
🎯 三、面试官常问对比题(高频)
❓ 1:provide/inject 与 props 的区别?
面试最佳答案:
- props 是父 → 子单层传递,适合常规通信
- provide/inject 是跨层级传递,不用每层都写 props
- provide 默认非响应式,需要 ref 才能响应式
- props 是强约束(类型校验),inject 是弱约束(不保证存在)
❓ 2:v-model 原理是什么?
答:
Vue3 中 v-model 等价于
modelValue+update:modelValue本质仍然是父传值、子触发更新事件。
❓ 3:expose 为什么需要?
因为:
- Vue3 的 setup 隔离作用域,默认不会暴露内部实例方法
- 使用 expose 可以决定哪些方法安全地开放给外部
❓ 4:为什么要用 store(Pinia)?不能用 eventBus 吗?
简答:
- eventBus 是事件驱动,不适合作状态管理
- Pinia 是响应式数据管理,有 devtools、有模块化、有持久化
- 大项目必须用 store
❓ 5:provide / inject 为什么默认不是响应式?
因为当初设计它用于"静态配置"(比如主题),要避免不必要更新;
若需要响应式,需要传 ref 或 reactive:
less
provide('theme', reactive({ mode: 'dark' }))
🎯 四、一句话总结
Vue3 的通信体系基于 props/emit 构建,
provide/inject 解决跨层级传递,
expose 解决实例暴露,
store 解决全局共享,
eventBus 解决简单事件分发。
掌握这些,你能应对所有组件通信场景。