Vue3 组件通信全攻略:12种方式与实战示例
在Vue3中,组件通信是构建应用的核心技能。本文系统梳理12种通信方式,从基础到进阶,结合真实场景与代码示例,帮助开发者灵活选择最佳方案。
一、父子组件通信
1. Props + Emit(基础单向数据流)
场景 :父组件传递数据给子组件,子组件触发事件通知父组件
示例:
javascript
// Parent.vue
<template>
<Child :message="parentMsg" @update="handleUpdate"/>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const parentMsg = ref('Hello from Parent')
const handleUpdate = (newMsg) => {
parentMsg.value = newMsg
}
</script>
// Child.vue
<template>
<div>{{ message }} <button @click="sendMessage">Send</button></div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
message: String
})
const emit = defineEmits(['update'])
const sendMessage = () => {
emit('update', 'Hello from Child')
}
</script>
2. $attrs(透传未声明的属性)
场景 :子组件需要接收父组件传递的非Props属性
示例:
javascript
// Parent.vue
<template>
<Child :msg="message" :other="extraData" />
</template>
<script setup>
import Child from './Child.vue'
const message = 'Main Message'
const extraData = { id: 123 }
</script>
// Child.vue
<template>
<div v-bind="$attrs"></div>
</template>
<script setup>
// $attrs自动包含父组件传递的other属性
</script>
3. Ref + DefineExpose(直接调用子组件方法)
场景 :父组件需要直接调用子组件的方法
示例:
javascript
// Parent.vue
<template>
<Child ref="childRef" />
<button @click="callChildMethod">Call Child</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
const callChildMethod = () => {
childRef.value.publicMethod()
}
</script>
// Child.vue
<template>
<div>Child Component</div>
</template>
<script setup>
import { defineExpose } from 'vue'
const publicMethod = () => {
console.log('Child method called')
}
defineExpose({ publicMethod })
</script>
二、兄弟组件通信
1. Mitt(轻量级事件总线)
场景 :平级组件间需要事件通知
示例:
javascript
// emitter.js
import mitt from 'mitt'
export const emitter = mitt()
// BrotherA.vue
<script setup>
import { emitter } from './emitter'
emitter.on('notify', (data) => {
console.log('BrotherA收到消息:', data)
})
</script>
// BrotherB.vue
<script setup>
import { emitter } from './emitter'
setTimeout(() => {
emitter.emit('notify', { msg: 'Hello BrotherA' })
}, 1000)
</script>
2. 共享父组件状态(通过$parent)
场景 :兄弟组件通过共同的父组件共享数据
示例:
javascript
// Parent.vue
<template>
<BrotherA :shared="state" />
<BrotherB :shared="state" />
</template>
<script setup>
import { reactive } from 'vue'
import BrotherA from './BrotherA.vue'
import BrotherB from './BrotherB.vue'
const state = reactive({ count: 0 })
</script>
// BrotherA.vue
<template>
<button @click="increment">Increment</button>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
shared: Object
})
function increment() {
props.shared.count++
}
</script>
三、跨层级组件通信
1. Provide + Inject(祖孙组件数据传递)
场景 :祖先组件向后代组件传递数据,无需逐层传递
示例:
javascript
// Ancestor.vue
<template>
<GrandChild />
</template>
<script setup>
import { provide, ref } from 'vue'
import GrandChild from './GrandChild.vue'
const theme = ref('dark')
provide('theme', theme)
</script>
// GrandChild.vue(隔代组件)
<template>
<div>Theme: {{ theme }}</div>
</template>
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
</script>
四、高级场景通信
1. V-Model 双向绑定(简化父子交互)
场景 :父组件与子组件之间的双向数据同步
示例:
javascript
// Parent.vue
<template>
<Child v-model="value" />
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const value = ref('Initial Value')
</script>
// Child.vue
<template>
<input :value="modelValue" @input="updateValue($event.target.value)" />
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const updateValue = (val) => {
emit('update:modelValue', val)
}
</script>
2. 依赖注入(函数/对象传递)
场景 :通过插槽传递函数或配置对象
示例:
javascript
// Parent.vue
<template>
<Child>
<template #default="{ config }">
<div :style="config">{{ config.title }}</div>
</template>
</Child>
</template>
<script setup>
import Child from './Child.vue'
</script>
// Child.vue
<template>
<div>
<slot :config="config">
Default Content
</slot>
</div>
</template>
<script setup>
import { reactive } from 'vue'
const config = reactive({ title: 'Slot Injected Config' })
</script>
五、其他补充方案
方式 | 适用场景 | 示例代码片段 |
---|---|---|
Vuex/Pinia | 全局状态管理 | store.commit('increment') / useCounterStore().increment() |
LocalStorage | 持久化数据共享 | localStorage.setItem('key', JSON.stringify(data)) |
Window全局对象 | 临时跨组件通信(慎用) | window.appData = {...} |
ES6 Module Import | 常量数据共享 | import { CONSTANT } from '@/constants' |
六、最佳实践建议
- 优先选择标准化方案:Props+Emit满足80%场景,复杂场景再用Provide/Mitt/状态管理
- 避免过度使用全局状态:仅当数据需要在多组件间共享时使用Vuex/Pinia
- 善用$attrs:减少子组件Props定义,实现属性透传
- 谨慎操作Ref:仅在必要时直接调用子组件方法,保持组件解耦
- 事件命名规范 :使用
on[EventName]
监听自定义事件,如on:update
七、扩展学习资源
- Vue3官方文档 - 组件通信
- Mitt官方文档
- Pinia状态管理指南
- Vue3组合式API详解
通过本文,你可以掌握Vue3组件通信的核心方法,并根据实际场景选择最优方案。建议将示例代码复制到Vue3项目中运行,观察不同方式的数据流向和效果差异。