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: 默认为 value prop 和 input event。
  • Vue 3: 默认为 modelValue prop 和 update:modelValue event。支持多个 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. parent/parent / 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/$off API,体现你对新版本的关注。
  • 状态管理选型:提到 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 (推荐)⭐⭐⭐