Vue 组件间通信方式
一、 父子组件通信 (最常用)
1. Props (父传子)
这是最基础的方式。父组件通过属性绑定传递数据,子组件通过 props 接收。
- 适用场景:通用的数据传递。
- 原则:单向数据流,子组件不应直接修改 Props。
text
12345
// Child.vue
defineProps(['msg'])
// Parent.vue
<Child msg="Hello Vue 3" />
2. $emit (子传父)
子组件通过触发事件,将数据传递给父组件。
- 适用场景:子组件状态变化通知父组件。
text
12345678
// Child.vue
const emit = defineEmits(['update'])
function handleClick() {
emit('update', 'New Data')
}
// Parent.vue
<Child @update="handleUpdate" />
3. v-model (双向绑定)
本质是 props + emit 的语法糖。
- Vue 2: 默认为
valueprop 和inputevent。 - Vue 3: 默认为
modelValueprop 和update:modelValueevent。支持多个 v-model。
4. $refs / defineExpose
父组件可以通过 ref 获取子组件实例,直接调用子组件的方法或访问数据。
- 注意:Vue 3 中使用
<script setup>的组件默认是关闭的,必须通过defineExpose暴露属性。
text
1234567
// Child.vue
const count = ref(0)
defineExpose({ count })
// Parent.vue
const childRef = ref(null)
console.log(childRef.value.count)
5. children (不推荐频繁使用)
- Vue 2: 使用
$children访问子组件实例数组。 - Vue 3:
$children已被移除。建议使用 refs。 $parent依然可用,但会导致组件耦合度过高。
二、 跨级/祖孙组件通信
1. Provide / Inject (依赖注入)
祖先组件作为“依赖提供者”,后代组件(无论多深)都可以注入该依赖。
- 适用场景:编写插件、组件库或深层嵌套结构(不需要通过中间层透传 props)。
- 特点:非响应式(除非传递的是 ref/reactive 对象)。
text
12345
// Ancestor.vue
provide('theme', 'dark')
// Descendant.vue
const theme = inject('theme')
2. $attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。
- 适用场景:高阶组件或二次封装组件,使用
v-bind="$attrs"将属性透传给内部组件。
三、 兄弟组件或任意组件通信
1. 全局状态管理 (Vuex / Pinia)
这是大型项目的标准解决方案。
- Vuex: Vue 2 的主流,包含 State, Getter, Mutation, Action, Module。
- Pinia: Vue 3 官方推荐(也支持 Vue 2),更轻量,去掉了 Mutation,支持 Composition API,对 TypeScript 支持更好。
2. EventBus (事件总线)
创建一个空的 Vue 实例作为中央事件总线,用 $emit 和 $on 通信。
- Vue 2:
Vue.prototype.$bus = new Vue()。 - Vue 3: 已移除
$on,$off等实例方法。官方推荐使用第三方库如mitt或tiny-emitter。
3. Mitt (Vue 3 替代 EventBus)
text
123456
import mitt from 'mitt'
const emitter = mitt()
// 监听
emitter.on('foo', e => console.log(e))
// 触发
emitter.emit('foo', { a: 'b' })
四、 面试高分总结策略 (Cheat Sheet)
如果面试官问:“Vue 组件间传值有哪些方法?”,你可以这样回答:
1. 开场定调
“Vue 的组件通信方式主要取决于组件之间的关系。我一般把它们分为三类:父子通信、跨级通信和全局通信。”
2. 核心展开 (由浅入深)
- 父子关系:最常用的是
props和$emit,这是符合单向数据流的最佳实践。如果是双向同步,我会用v-model。如果是封装组件需要透传属性,我会用$attrs。偶尔为了操作子组件内部方法,会配合ref和defineExpose。- 跨级/祖孙关系:对于深层嵌套,为了避免 props 逐层传递(Props Drilling),我会使用
provide和inject,这在开发通用组件库时非常有用。- 兄弟或任意关系:如果是简单的场景,Vue 2 可以用 EventBus,但在 Vue 3 中我推荐使用
mitt。如果是复杂的应用状态管理,Pinia 是目前的首选(Vuex 是过去式),因为它对 TS 支持更好且 API 更简洁。
3. 补充亮点 (体现深度)
- Vue 3 的变化:特意提到 Vue 3 移除了
$children和$on/$offAPI,体现你对新版本的关注。- 状态管理选型:提到 Pinia 相比 Vuex 的优势(去掉了 mutation,体积更小)。
- 浏览器存储:如果数据需要持久化,还可以补充 localStorage / sessionStorage 作为辅助通信手段。
五、 简单对比表
| 方式 | 适用场景 | Vue Version | 复杂度 |
|---|---|---|---|
| Props / Emit | 父子组件 | All | ⭐ (低) |
| v-model | 父子双向同步 | All (语法略有不同) | ⭐ |
| Refs / Expose | 父调子方法 | All | ⭐ |
| Provide / Inject | 跨级/插件 | All | ⭐⭐ |
| Attributes ($attrs) | 组件封装/透传 | All | ⭐⭐ |
| EventBus ($bus) | 任意组件 | Vue 2 Only | ⭐⭐ |
| Mitt / Tiny-emitter | 任意组件 | Vue 3 (替代 EventBus) | ⭐⭐ |
| Vuex | 全局状态 | Vue 2 (Vue 3可用) | ⭐⭐⭐ (高) |
| Pinia | 全局状态 | Vue 3 (推荐) | ⭐⭐⭐ |