在 Vue 项目中,组件通信需根据组件层级关系选择合适方案,不同场景对应不同的 API,既能保证代码简洁性,又能避免过度设计。以下是四种核心通信方案的详细实现与适用场景:
(1)父子组件:props + emit(最基础、最高频)
父子组件是最常见的组件关系,通过props实现父向子传值,emit实现子向父传值,两者结合能满足 90% 以上的简单交互需求。
<!-- 父组件 Parent.vue:传递数据与接收子组件事件 -->
<template>
<div class="parent-container">
<h3>父组件</h3>
<!-- 1. 父向子传值:通过props传递静态值或响应式数据 -->
<Child
:static-msg="'静态文本'"
:dynamic-count="count"
:user-info="userInfo"
<!-- 2. 子向父传值:通过v-on监听子组件触发的事件 -->
@update-count="handleUpdateCount"
@send-message="handleReceiveMessage"
/>
<p>子组件传递的消息:{{ childMessage }}</p>
</div>
</template>
<script setup>
import Child from './Child.vue'
import { ref, reactive } from 'vue'
// 父组件的响应式数据
const count = ref(0)
const userInfo = reactive({ name: '父组件用户', age: 30 })
const childMessage = ref('')
// 接收子组件触发的“update-count”事件
const handleUpdateCount = (newCount) => {
count.value = newCount
console.log('父组件接收更新后的计数:', newCount)
}
// 接收子组件触发的“send-message”事件
const handleReceiveMessage = (msg) => {
childMessage.value = msg
}
</script>
<style scoped>
.parent-container {
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
margin: 10px;
}
</style><!-- 子组件 Child.vue:接收props与触发事件 -->
<template>
<div class="child-container">
<h4>子组件</h4>
<!-- 使用父组件传递的props数据 -->
<p>静态消息:{{ staticMsg }}</p>
<p>动态计数:{{ dynamicCount }}</p>
<p>用户信息:{{ userInfo.name }}({{ userInfo.age }}岁)</p>
<!-- 触发事件向父组件传值 -->
<button @click="sendUpdateCount">更新父组件计数</button>
<button @click="sendMessageToParent" style="margin-left: 10px;">发送消息</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
// 1. 定义接收的props:指定类型、默认值、必填项,增强类型校验
const props = defineProps({
staticMsg: {
type: String,
required: true
},
dynamicCount: {
type: Number,
default: 0
},
userInfo: {
type: Object,
required: true,
// 自定义校验规则:确保userInfo包含name属性
validator: (value) => {
return 'name' in value
}
}
})
// 2. 定义可触发的事件:明确事件名与参数类型
const emit = defineEmits(['update-count', 'send-message'])
// 触发“update-count”事件,传递新计数
const sendUpdateCount = () => {
const newCount = props.dynamicCount + 1
emit('update-count', newCount) // 第二个参数为传递给父组件的数据
}
// 触发“send-message”事件,传递消息
const sendMessageToParent = () => {
emit('send-message', '来自子组件的问候!')
}
</script>
<style scoped>
.child-container {
padding: 15px;
border: 1px solid #eee;
border-radius: 6px;
margin-top: 10px;
}
button {
padding: 6px 12px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>(2)兄弟组件:mitt 事件总线(轻量场景)
当两个组件为平级关系(如同一父组件下的子组件),且交互不频繁时,可使用mitt库实现事件通信,避免引入复杂的状态管理。
# 安装mitt库
npm install mitt
// 1. 创建事件总线实例(src/utils/eventBus.js)
import mitt from 'mitt'
// 创建并导出事件总线
export const eventBus = mitt()<!-- 兄弟组件A BrotherA.vue:发送事件 -->
<template>
<div class="brother-a">
<h4>兄弟组件A</h4>
<button @click="sendToBrotherB">向兄弟组件B发送消息</button>
</div>
</template>
<script setup>
import { eventBus } from '@/utils/eventBus'
const sendToBrotherB = () => {
// 触发“brother-message”事件,传递数据
eventBus.emit('brother-message', {
content: '来自A的消息',
time: new Date().toLocaleTimeString()
})
}
</script><!-- 兄弟组件B BrotherB.vue:接收事件 -->
<template>
<div class="brother-b">
<h4>兄弟组件B</h4>
<p v-if="message">收到A的消息:{{ message.content }}({{ message.time }})</p>
<p v-else>未收到消息</p>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
import { eventBus } from '@/utils/eventBus'
const message = ref(null)
// 监听“brother-message”事件
const messageHandler = (data) => {
message.value = data
}
eventBus.on('brother-message', messageHandler)
// 组件卸载时移除监听,避免内存泄漏
onUnmounted(() => {
eventBus.off('brother-message', messageHandler)
})
</script>(3)全局状态:Pinia(中大型项目)
当多个组件共享复杂状态(如用户登录信息、购物车数据)时,Pinia 是 Vue 官方推荐的状态管理库,相比 Vuex 简化了语法,支持 TypeScript,且集成了 Vue3 的 Composition API。
# 安装Pinia
npm install pinia
// 1. 创建Pinia实例(src/store/index.js)
import { createPinia } from 'pinia'
export const pinia = createPinia()
