# Vue组件间的通讯方式有哪些
## 前言
在Vue应用开发中,组件化是核心思想之一。随着应用规模的增长,组件间的数据传递和状态管理变得尤为重要。本文将全面剖析Vue中8种常用的组件通讯方式,从基础到高级,结合实际开发场景进行深度解析。
## 一、Props / $emit(父子组件通讯)
### 1.1 基本使用
```html
<!-- 父组件 -->
<template>
<child-component :title="parentTitle" @update-title="handleUpdate"/>
</template>
<script>
export default {
data() {
return {
parentTitle: '初始标题'
}
},
methods: {
handleUpdate(newTitle) {
this.parentTitle = newTitle
}
}
}
</script>
<!-- 子组件 -->
<script>
export default {
props: {
title: {
type: String,
default: '默认标题'
}
},
methods: {
changeTitle() {
this.$emit('update-title', '新标题')
}
}
}
</script>
// 父组件访问子组件
this.$refs.childComponent.methodName()
// 子组件访问父组件(不推荐)
this.$parent.parentMethod()
// 访问根实例
this.$root.rootMethod()
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// 组件A(发送事件)
EventBus.$emit('custom-event', payload)
// 组件B(接收事件)
EventBus.$on('custom-event', payload => {
// 处理逻辑
})
// 避免内存泄漏
beforeDestroy() {
EventBus.$off('custom-event', this.eventHandler)
}
优点: - 任意组件间通信 - 简单快速实现跨级通讯
缺点: - 难以追踪事件来源 - 大型项目中可能导致事件混乱
// 祖先组件
export default {
provide() {
return {
theme: this.themeData
}
}
}
// 后代组件
export default {
inject: ['theme']
}
provide() {
return {
getTheme: () => this.themeData
}
}
<!-- 父组件 -->
<child-component v-bind="$attrs" v-on="$listeners"/>
<!-- 子组件 -->
<script>
export default {
inheritAttrs: false, // 禁用默认绑定
mounted() {
console.log(this.$attrs) // 包含所有非prop属性
}
}
</script>
// store.js
export default new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
})
const moduleA = {
namespaced: true,
state: { ... },
mutations: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
特性 | EventBus | mitt |
---|---|---|
体积 | 较大 | 极简(200b) |
Vue3支持 | 否 | 是 |
类型支持 | 有限 | 优秀 |
import mitt from 'mitt'
const emitter = mitt()
// 发送事件
emitter.emit('foo', { a: 'b' })
// 监听事件
emitter.on('foo', e => console.log(e))
// 清除所有
emitter.all.clear()
// sharedState.js
import { reactive } from 'vue'
export const state = reactive({
count: 0
})
// 组件A
import { state } from './sharedState'
state.count++
// 组件B
import { state } from './sharedState'
console.log(state.count)
// 祖先组件
import { provide, ref } from 'vue'
setup() {
const location = ref('North Pole')
provide('location', location)
}
// 后代组件
import { inject } from 'vue'
setup() {
const location = inject('location')
return { location }
}
场景 | 推荐方案 | 理由 |
---|---|---|
严格父子关系 | props/emit | 显式声明,易于维护 |
跨多级组件 | provide/inject | 避免prop逐层传递 |
全局状态管理 | Vuex/Pinia | 集中式管理,支持调试工具 |
临时事件通知 | EventBus/mitt | 轻量快捷 |
组件库开发 | \(attrs/\)listeners | 灵活处理未知属性和事件 |
graph TD
A[商品列表] -->|props| B(商品卡片)
B -->|emit| A[加入购物车]
C[购物车] -->|Vuex| D[结算页面]
E[用户中心] -->|provide/inject| F[会员等级组件]
// 错误做法
provide: {
staticValue: this.nonReactiveData
}
// 正确做法
provide() {
return {
reactiveValue: computed(() => this.reactiveData)
}
}
建议采用命名空间:
// 代替 'update'
this.$emit('user:update', data)
// 代替 'delete'
this.$emit('order:delete', id)
移除API:
新增特性: