Vue 组件通信的主流方案

春秋大王2025-10-103 次阅读
Vue 组件通信的主流方案

在 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()