在Vue中,组件间通信是开发复杂应用的核心问题。根据组件关系(父子、兄弟、跨级、全局)的不同,通信方式也有所区别。以下是Vue组件间通信的8种常用方式。
一、父子组件通信
- Props(父->子)
- 父组件通过
props
向子组件传递数据。
html
<!-- 父组件 -->
<Child :title="parentTitle" />
<!-- 子组件 -->
<script>
export default {
props: ['title'] // 接收数据
}
</script>
注意:Props是单向数据流,子组件不能直接修改。
- $emit/v-on(子->父)
- 子组件通过
$emit
触发事件,父组件通过v-on
监听。
html
<!-- 父组件 -->
<Child @update="handleUpdate" />
<!-- 子组件 -->
<button @click="$emit('update', newValue)">提交</button>
二、兄弟组件通信
- 共同父组件(Event Bus 模式)
- 通过共同的父组件作为中介:
javascript
// 父组件
<ChildA @event="handleEvent" />
<ChildB :data="sharedData" />
// ChildA 触发事件
this.$emit('event', data)
// 父组件讲数据传递给 ChildB
- Event Bus(全局事件总线)
Event Bus是一种发布-订阅模式的通信方式,适用于任意组件间通信(父子、兄弟、跨级),无需依赖共同的父组件或Vuex/Pinia。
- 创建Event Bus
首先,创建一个全局的Vue实例作为事件中心(通常在bus.js
文件中定义):
javascript
// src/utils/bus.js
import Vue from 'vue';
export const bus = new Vue(); // 导出一个Vue实例
- 发送事件( e m i t )在需要发送数据的组件中,使用 ' b u s . emit) 在需要发送数据的组件中,使用`bus. emit)在需要发送数据的组件中,使用'bus.emit('事件名',数据)`:
javascript
<!-- ComponentA.vue -->
<script>
import { bus } from '@utils/bus';
export default {
methods: {
sendMessage() {
bus.$emit('message', 'Hello from ComponentA!');
}
}
}
</script>
<template>
<button @click="sendMessage">发送消息</button>
</template>
- 监听事件( o n )在需要接收数据的组件中,使用 ' b u s . on) 在需要接收数据的组件中,使用`bus. on)在需要接收数据的组件中,使用'bus.on('事件名', callback)`监听事件:
javascript
<!-- ComponentB.vue -->
<script>
import { bus } from '@/utils/bus';
export default {
created() {
bus.$on('message', (data) => {
console.log('收到消息:', data); // "Hello from ComponentA!"
})
},
beforeDestroy() {
// 组件销毁时,移除监听,避免内存泄漏
bus.$off('message');
}
}
</script>
- 移除监听($off)
为了避免内存泄漏,需要在组件销毁时移除监听:
javascript
beforeDestory() {
bus.$off('message'); // 移除单个事件监听
// bus.$off(); // 移除所有监听(慎用)
}
- 完整示例
场景:
ComponentA 发送消息
ComponentB 和 ComponentC 接收消息
(1)bus.js
javascript
import Vue from 'vue';
export const bus = new Vue();
(2)ComponentA.vue(发送方)
javascript
<script>
import { bus } from '@/utils/bus';
export default {
methods: {
sendData() {
bus.$emit('update', { text: 'Hello Event Bus!' });
}
}
}
</script>
<template>
<button @click="sendData">发送数据</button>
</template>
(3)ComponentB.vue(接收方1)
javascript
<script>
import { bus } from '@/utils/bus';
export default {
data() {
return {
receivedData: null
};
},
created() {
bus.$on('update', (data) => {
this.receivedData = data.text;
});
},
beforeDestroy() {
bus.$off('update');
}
}
</script>
<template>
<div>收到数据: {{ receivedData }}</div>
</template>
(4)ComponentC.vue(接收方2)
javascript
<script>
import { bus } from '@/utils/bus';
export default {
created() {
bus.$on('update', (data) => {
alert(`ComponentC 收到: ${data.text}`);
});
},
beforeDestroy() {
bus.$off('update');
}
}
</script>
-
适用场景
✅ 任意组件间通信(父子、兄弟、跨级)
✅ 简单项目,不想引入 Vuex/Pinia
❌ 大型项目(建议用 Vuex/Pinia,Event Bus 难以维护)
-
注意事项
- 内存泄漏:必须用 beforeDestroy 或 onUnmounted(Vue 3)移除监听。
- 调试困难:事件全局触发,难以追踪来源。
- 替代方案:
Vue 2:Vue.observable(轻量状态管理)
Vue 3:mitt(更小更快的 Event Bus 库)
三、跨级组件通信
- Provide/Inject
- 祖先组件通过
provide
提供数据,后代组件通过inject
注入:
javascript
// 祖先组件
export default {
provide() {
return { theme: 'dark' };
}
}
// 后代组件
export default {
inject: ['theme'] // 直接适用this.theme
}
适用场景:深层嵌套组件(如 UI 库的主题配置)。
- attrs/listeners
在Vue中,$attrs
和$listeners
用于处理跨级组件通信 和透传属性和事件,特别适用于封装高阶组件(如自定义表单控件、UI组件库)。以下是详细示例和解析:
-
$attrs
和$listeners
的作用
$attrs
:包含父组件传递的、未被props
接收的非class/style属性,vue3中需v-bind="$attrs"
$listeners
:包含父组件传递的所有事件监听器(Vue2特有) -
Vue2示例:透传属性和事件
场景父组件-->中间组件-->子组件,透传
placeholder
属性和focus
事件。(1) 父组件 (
Parent.vue
)
html
<template>
<MiddleComponent placeholder="请输入用户名" @focus="handleFocus" />
</template>
<script>
export default {
methods: {
handleFocus() {
console.log("输入框获取焦点");
}
}
}
</script>
(2) 中间组件 (MiddleComponent.vue
)
html
<template>
<!-- 透传所有属性和事件到子组件 -->
<ChildComponet v-bind="$attrs" v-on="$listeners" />
</template>
<script>
export default {
// 不声明props,让$atts接收所有属性
}
</script>
(3) 子组件 (ChildComponent.vue
)
html
<template>
<input :placeholder="$attrs.placeholder" <!-- 直接使用 $attrs -->
@focus="$listeners.focus" <!-- 触发父组件事件 -->
</template>
关键点:
-
中间组件不声明
props
,让$attrs
自动接收所有未声明的属性。 -
用
v-bind="$attrs"
和v-on="$listeners"
透传到子组件。
- Vue3示例(
$attrs
包含事件)
Vue3删除了$listeners
,所有事件也通过$attrs
传递。
(1) 父组件 (Parent.vue)
html
<template>
<MiddleComponent
placeholder="请输入密码"
@focus="handleFocus"
/>
</template>
(2) 中间组件 (MiddleComponent.vue)
html
<template>
<ChildComponent v-bind="$attrs" />
</template>
(3) 子组件 (ChildComponent.vue)
html
<template>
<input
:placeholder="$attrs.placeholder"
@focus="$attrs.onFocus" <!-- Vue 3 事件名变为 onXxx -->
/>
</template>
Vue3变化:
- 事件监听器在
$attrs
中以onXxx
形式存在(如@focus
->onFocus
)。 - 不再需要
v-on="$listeners"
。
- 高级用法:选择性透传
如果中间组件需要拦截部分属性/事件,可以手动筛选:
中间组件 (MiddleComponent.vue
)
html
<template>
<ChildComponent
:placeholder="$attrs.placeholder"
@custom-event="handleCustomEvent"
<!-- 只透传特定事件 -->
v-on="filteredListeners"
/>
</template>
<script>
export default {
computed: {
filteredListeners() {
const { focus, ...rest } = this.$listeners; // 过滤掉 focus 事件
return rest;
}
},
methods: {
handleCustomEvent() {
console.log("拦截自定义事件");
}
}
}
</script>
- 常见问题
Q1: 为什么$attrs
不包含class
和style
? - Vue 默认将
class
和style
绑定到组件的根元素,如需透传需手动处理:
html
<ChildCompoent :class="$attrs.class" />
Q2: 如何避免属性重复绑定?
- 如果子组件的根元素已经绑定了
$attrs
,可以用inheritAttrs: false
禁止默认行为:
javascript
export default {
inheritAttrs: false // 阻止自动绑定到根元素
}
- 适用场景
- 封装表单控制(如自定义input):
$attrs
+$listeners
- 高阶组件(HOC): 透传所有未处理属性/事件
- UI组件库开发:避免属性丢失,保持灵活性
总结: - Vue 2:attrs(属性) + listeners(事件)分别透传。
- Vue 3:$attrs 包含属性和事件(事件名变为 onXxx)。
- 最佳实践:
中间组件用 v-bind="attrs" 透传属性。 用 inheritAttrs: false 避免重复绑定。 需要拦截时手动筛选 attrs 或 $listeners。
四、全局状态管理
- Vuex(官方状态管理)
- 集中式存储管理所有组件的状态:
javascript
// store.js
export default new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) { state.count++; }
}
});
// 组件中使用
this.$store.commit('increment');
console.log(this.$store.state.count);
适用场景:中大型应用,需要共享状态的复杂场景。
- Pinia(Vue 3 推荐)
- Vuex的替代方案,更简洁的API:
javascript
// stores/counter.js
export const useCounterStore = defineStore('counter', {
state: () => ({count: 0}),
actions: {
increment() { this.count++; }
}
});
// 组件中使用
const counter = useCounterStore();
counter.increment();
五、其他方式
- $refs: 直接访问子组件实例(禁耦合,慎用)。
- LocalStorage/SesstionStorage: 持久化存储(非响应式,需手动监听)。
- Vue3的
setup
+defineExpose
:暴露子组件方法。
选择通信方式的准则
- 父子组件:优先用 props + $emit。
- 兄弟组件:用共同的父组件或 Event Bus。
- 跨级组件:用 provide/inject 或 Vuex/Pinia。
- 全局状态:Vuex(Vue 2)或 Pinia(Vue 3)。