vue组件间通信
在Vue中,组件间通信是开发复杂应用的核心问题。根据组件关系(父子、兄弟、跨级、全局)的不同,通信方式也有所区别。以下是Vue组件间通信的8种常用方式。
一、父子组件通信
- Props(父->子)
- 父组件通过
props
向子组件传递数据。
<!-- 父组件 -->
<Child :title="parentTitle" /><!-- 子组件 -->
<script>
export default {props: ['title'] // 接收数据
}
</script>
注意:Props是单向数据流,子组件不能直接修改。
2. $emit/v-on(子->父)
- 子组件通过
$emit
触发事件,父组件通过v-on
监听。
<!-- 父组件 -->
<Child @update="handleUpdate" /><!-- 子组件 -->
<button @click="$emit('update', newValue)">提交</button>
二、兄弟组件通信
- 共同父组件(Event Bus 模式)
- 通过共同的父组件作为中介:
// 父组件
<ChildA @event="handleEvent" />
<ChildB :data="sharedData" />// ChildA 触发事件
this.$emit('event', data)// 父组件讲数据传递给 ChildB
- Event Bus(全局事件总线)
Event Bus是一种发布-订阅模式的通信方式,适用于任意组件间通信(父子、兄弟、跨级),无需依赖共同的父组件或Vuex/Pinia。
- 创建Event Bus
首先,创建一个全局的Vue实例作为事件中心(通常在bus.js
文件中定义):
// 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('事件名‘,数据)`:
<!-- 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)`监听事件:
<!-- 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)
为了避免内存泄漏,需要在组件销毁时移除监听:
beforeDestory() {bus.$off('message'); // 移除单个事件监听// bus.$off(); // 移除所有监听(慎用)
}
- 完整示例
场景:
ComponentA 发送消息
ComponentB 和 ComponentC 接收消息
(1)bus.js
import Vue from 'vue';
export const bus = new Vue();
(2)ComponentA.vue(发送方)
<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)
<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)
<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
注入:
// 祖先组件
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
)
<template><MiddleComponent placeholder="请输入用户名" @focus="handleFocus" />
</template><script>
export default {methods: {handleFocus() {console.log("输入框获取焦点");}}
}
</script>
(2) 中间组件 (MiddleComponent.vue
)
<template><!-- 透传所有属性和事件到子组件 --><ChildComponet v-bind="$attrs" v-on="$listeners" />
</template>
<script>
export default {// 不声明props,让$atts接收所有属性
}
</script>
(3) 子组件 (ChildComponent.vue
)
<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)
<template><MiddleComponentplaceholder="请输入密码"@focus="handleFocus"/>
</template>
(2) 中间组件 (MiddleComponent.vue)
<template><ChildComponent v-bind="$attrs" />
</template>
(3) 子组件 (ChildComponent.vue)
<template><input:placeholder="$attrs.placeholder"@focus="$attrs.onFocus" <!-- Vue 3 事件名变为 onXxx -->/>
</template>
Vue3变化:
- 事件监听器在
$attrs
中以onXxx
形式存在(如@focus
->onFocus
)。 - 不再需要
v-on="$listeners"
。
- 高级用法:选择性透传
如果中间组件需要拦截部分属性/事件,可以手动筛选:
中间组件 (MiddleComponent.vue
)
<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
绑定到组件的根元素,如需透传需手动处理:
<ChildCompoent :class="$attrs.class" />
Q2: 如何避免属性重复绑定?
- 如果子组件的根元素已经绑定了
$attrs
,可以用inheritAttrs: false
禁止默认行为:
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(官方状态管理)
- 集中式存储管理所有组件的状态:
// 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:
// 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)。