Vue 组件通信是构建复杂应用的基础,以下是 Vue 2 和 Vue 3 中所有主要的组件通信方式及其适用场景:
一、父子组件通信
1. Props(父 → 子)
vue
<!-- 父组件 -->
<ChildComponent :title="pageTitle" :user="userData" />
<!-- 子组件 -->
<script>
export default {
props: {
title: String,
user: {
type: Object,
default: () => ({})
}
}
}
</script>
特点:单向数据流,适合父向子传值
2. $emit / 自定义事件(子 → 父)
vue
复制
xml
<!-- 子组件 -->
<button @click="$emit('update', newValue)">提交</button>
<!-- 父组件 -->
<ChildComponent @update="handleUpdate" />
特点:子组件触发父组件方法
3. v-model(双向绑定)
vue
<!-- Vue 3 默认方式 -->
<CustomInput v-model="searchText" />
<!-- 子组件实现 -->
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
二、跨层级组件通信
4. Provide / Inject(依赖注入)
javascript
// 祖先组件 (Vue 3)
import { provide } from 'vue'
export default {
setup() {
provide('theme', 'dark')
}
}
// 后代组件
import { inject } from 'vue'
export default {
setup() {
const theme = inject('theme', 'light') // 默认值
return { theme }
}
}
特点:适合深层嵌套组件通信
5. 事件总线(Event Bus)
javascript
// Vue 3 需要外部库(如 mitt)
// bus.js
import mitt from 'mitt'
export default mitt()
// 组件A(发送)
import bus from './bus'
bus.emit('event-name', data)
// 组件B(接收)
bus.on('event-name', data => { ... })
// 记得在组件卸载时移除监听
onUnmounted(() => {
bus.off('event-name')
})
特点:任意组件间通信,但需注意内存泄漏
三、状态管理方案
6. Vuex(Vue 2 官方状态管理)
javascript
// store.js
import { createStore } from 'vuex'
export default createStore({
state: { count: 0 },
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => commit('increment'), 1000)
}
}
})
// 组件中使用
import { useStore } from 'vuex'
export default {
setup() {
const store = useStore()
return {
count: computed(() => store.state.count),
increment: () => store.commit('increment')
}
}
}
7. Pinia(Vue 3 推荐状态管理)
javascript
// stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
}
}
})
// 组件中使用
import { useCounterStore } from '@/stores/counter'
export default {
setup() {
const counter = useCounterStore()
return { counter }
}
}
特点:更简单、类型安全、组合式API友好
四、模板引用通信
8. ref / $refs
vue
<!-- 父组件 -->
<ChildComponent ref="child" />
<script>
export default {
mounted() {
this.$refs.child.methodName() // 调用子组件方法
this.$refs.child.property = value // 访问子组件属性
}
}
</script>
特点:直接访问组件实例,但应谨慎使用
五、其他通信方式
9. attrs/attrs/listeners(Vue 2)
vue
<!-- 父组件 -->
<ChildComponent v-bind="$attrs" v-on="$listeners" />
<!-- Vue 3 中已合并到 $attrs -->
10. parent/parent/children
javascript
// 子组件中
this.$parent.methodName()
// 父组件中
this.$children[0].property = value
特点:不推荐使用,破坏组件封装性
11. 全局状态(不推荐)
javascript
// main.js
app.config.globalProperties.$appName = 'My App'
// 组件中
this.$appName
六、通信方式选择指南
通信方式 | 适用场景 | Vue 2 | Vue 3 | 备注 |
---|---|---|---|---|
Props | 父→子数据传递 | ✅ | ✅ | 推荐首选 |
$emit | 子→父事件通知 | ✅ | ✅ | 推荐首选 |
v-model | 表单双向绑定 | ✅ | ✅ | 语法糖 |
Provide/Inject | 跨层级传递 | ✅ | ✅ | 适合主题/配置 |
事件总线 | 任意组件通信 | ✅ | ⚠️需库 | 注意内存泄漏 |
Vuex | 复杂状态管理 | ✅ | ⚠️兼容 | Vue 2首选 |
Pinia | 现代状态管理 | ❌ | ✅ | Vue 3首选 |
$refs | 直接访问组件 | ✅ | ✅ | 应急方案 |
$attrs | 属性透传 | ✅ | ✅ | 高阶组件 |
$parent | 访问父实例 | ✅ | ❌ | 不推荐 |
最佳实践建议
- 简单场景:优先使用 props + emit
- 兄弟组件:提升状态到共同父级或使用状态管理
- 跨层级:使用 Provide/Inject 或状态管理
- 全局状态:使用 Pinia/Vuex
- 避免:直接修改子组件状态($refs)、过度使用事件总线
- TypeScript:为所有通信方式添加类型定义
Vue 3 组合式API通信示例
javascript
// useCounter.js - 可组合函数
import { ref } from 'vue'
export function useCounter() {
const count = ref(0)
const increment = () => count.value++
return { count, increment }
}
// 组件A
import { useCounter } from './useCounter'
const { count, increment } = useCounter()
// 组件B
import { useCounter } from './useCounter'
const { count } = useCounter() // 共享状态
选择通信方式时应考虑组件关系、数据流清晰度和长期维护成本。对于新项目,Vue 3 + Pinia + 组合式API是最佳选择。