Vue3 组件通信全攻略:全部方式与实战示例
在Vue3中,组件通信是构建应用的核心技能。本文系统梳理多种通信方式,从基础到进阶,结合真实场景与代码示例,帮助开发者灵活选择最佳方案。
一、父子组件通信
1. Props + Emit(基础单向数据流)
场景:父组件传递数据给子组件,子组件触发事件通知父组件
示例:
// 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属性
示例:
// 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(父直接调用子组件方法)
场景:父组件需要直接访问子组件的属性或方法
示例:
// Parent.vue
<template>
<Child ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref(null)
const callChildMethod = () => {
childRef.value?.childFunction()
}
</script>
// Child.vue
<template>
<div>子组件内容</div>
</template>
<script setup>
import { defineExpose } from 'vue'
const childFunction = () => {
console.log('父组件调用了子组件方法')
}
// 显式暴露子组件方法(Vue3 setup默认私有)
defineExpose({ childFunction })
</script>
二、兄弟组件通信
1. 父组件中转(基础方式)
场景:两个同级子组件之间传递数据,通过共同父组件作为中间层
示例:
// Parent.vue
<template>
<BrotherA @sendData="handleDataFromA" />
<BrotherB :data="brotherData" />
</template>
<script setup>
import { ref } from 'vue'
import BrotherA from './BrotherA.vue'
import BrotherB from './BrotherB.vue'
const brotherData = ref('')
const handleDataFromA = (data) => {
brotherData.value = data
}
</script>
// BrotherA.vue(发送方)
<template>
<button @click="sendToBrother">给B传数据</button>
</template>
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['sendData'])
const sendToBrother = () => {
emit('sendData', '来自A的数据')
}
</script>
// BrotherB.vue(接收方)
<template>
<div>收到A的数据:{{ data }}</div>
</template>
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
data: String
})
</script>
2. EventBus(mitt库实现)
场景:多个非父子组件之间的轻量通信(Vue3移除内置EventBus,需用第三方库)
示例:
// 1. 安装mitt:npm install mitt
// 2. 创建eventBus.js
import mitt from 'mitt'
export const eventBus = mitt()
// 3. 发送方组件
<script setup>
import { eventBus } from './eventBus.js'
const sendMsg = () => {
eventBus.emit('msg-event', 'EventBus传递的消息')
}
</script>
// 4. 接收方组件
<script setup>
import { eventBus } from './eventBus.js'
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
// 监听事件
eventBus.on('msg-event', (msg) => {
console.log('收到消息:', msg)
})
})
onUnmounted(() => {
// 销毁事件(避免内存泄漏)
eventBus.off('msg-event')
})
</script>
三、跨层级组件通信
1. Provide / Inject(依赖注入)
场景:多层级组件(如爷孙)之间传递数据,无需逐层透传Props
示例:
// 顶层组件(Provider)
<script setup>
import { provide, ref } from 'vue'
const globalTheme = ref('light')
// 提供数据(key: 标识,value: 数据)
provide('theme-key', {
theme: globalTheme,
changeTheme: (newTheme) => {
globalTheme.value = newTheme
}
})
</script>
// 深层子组件(Injector)
<script setup>
import { inject } from 'vue'
// 注入数据(通过相同的key)
const themeConfig = inject('theme-key')
// 使用数据
console.log(themeConfig.theme.value) // light
// 调用方法
const switchTheme = () => {
themeConfig.changeTheme('dark')
}
</script>
2. Pinia(Vue3官方状态管理)
场景:大型应用中多组件共享状态(替代Vuex)
示例:
// 1. 安装Pinia:npm install pinia
// 2. 创建store(stores/counter.js)
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
}
},
getters: {
doubleCount: (state) => state.count * 2
}
})
// 3. 全局注册Pinia(main.js)
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
app.mount('#app')
// 4. 任意组件中使用
<script setup>
import { useCounterStore } from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counterStore = useCounterStore()
// 解构state(保持响应式)
const { count, doubleCount } = storeToRefs(counterStore)
// 调用action
const addCount = () => {
counterStore.increment()
}
</script>
四、其他进阶通信方式
1. Teleport + 状态共享
场景:Teleport组件(传送门)与原组件之间的通信
示例:
// 原组件
<template>
<button @click="openModal">打开弹窗</button>
<Teleport to="body">
<Modal :visible="isModalVisible" @close="isModalVisible = false" />
</Teleport>
</template>
<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const isModalVisible = ref(false)
const openModal = () => {
isModalVisible.value = true
}
</script>
// Modal.vue(Teleport目标组件)
<template>
<div v-if="visible" class="modal">
<div>弹窗内容</div>
<button @click="emit('close')">关闭</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
const props = defineProps({
visible: Boolean
})
const emit = defineEmits(['close'])
</script>
2. Vue Router(路由参数/Query)
场景:不同路由页面之间传递数据
示例:
// 跳转时传参(方式1:路由参数)
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const goToDetail = (id) => {
router.push({
path: `/detail/${id}`, // 需在路由配置中定义:path: '/detail/:id'
})
}
</script>
// 接收路由参数
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
// 响应式获取参数
const detailId = route.params.id
</script>
// 跳转时传参(方式2:Query参数)
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const goToSearch = (keyword) => {
router.push({
path: '/search',
query: { keyword }
})
}
</script>
// 接收Query参数
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const searchKeyword = route.query.keyword
</script>
3. Slots(作用域插槽传值)
场景:父组件通过插槽向子组件传递内容,同时子组件向父组件传递数据
示例:
// 子组件(提供插槽数据)
<template>
<div class="list">
<slot v-for="item in list" :item="item" :index="index">
<!-- 默认内容 -->
<div>{{ item.name }}</div>
</slot>
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' }
])
</script>
// 父组件(使用作用域插槽)
<template>
<ListComponent>
<template #default="{ item, index }">
<div>第{{ index + 1 }}项:{{ item.name }}(ID:{{ item.id }})</div>
</template>
</ListComponent>
</template>
<script setup>
import ListComponent from './ListComponent.vue'
</script>
4. v-model(组件双向绑定)
场景:父组件与子组件之间的双向数据同步(语法糖)
示例:
// 子组件
<template>
<input
type="text"
:value="modelValue"
@input="emit('update:modelValue', $event.target.value)"
>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
// v-model默认绑定modelValue
const props = defineProps({
modelValue: String
})
const emit = defineEmits(['update:modelValue'])
</script>
// 父组件使用
<template>
<CustomInput v-model="inputValue" />
<div>输入内容:{{ inputValue }}</div>
</template>
<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'
const inputValue = ref('')
</script>
5. Composition API 工具函数(抽离通信逻辑)
场景:多个组件复用相同的通信逻辑(如接口请求+状态共享)
示例:
// 抽离的工具函数(composables/useUser.js)
import { ref, onMounted } from 'vue'
import { getUserInfo } from '@/api/user'
export const useUser = () => {
const userInfo = ref(null)
const loading = ref(false)
const fetchUser = async () => {
loading.value = true
try {
const res = await getUserInfo()
userInfo.value = res.data
} catch (err) {
console.error('获取用户信息失败', err)
} finally {
loading.value = false
}
}
onMounted(fetchUser)
return { userInfo, loading, fetchUser }
}
// 任意组件中使用
<script setup>
import { useUser } from '@/composables/useUser'
const { userInfo, loading, fetchUser } = useUser()
</script>
6. Pinia 高级用法(Action跨组件调用)
场景:一个Pinia Store的Action调用另一个Store的方法,实现跨组件逻辑联动
示例:
// stores/user.js
import { defineStore } from 'pinia'
import { useCartStore } from './cart'
export const useUserStore = defineStore('user', {
state: () => ({
token: null
}),
actions: {
async login(loginForm) {
// 登录接口请求
const res = await loginApi(loginForm)
this.token = res.data.token
// 调用购物车Store的方法,同步用户购物车
const cartStore = useCartStore()
await cartStore.syncCart()
}
}
})
// stores/cart.js
import { defineStore } from 'pinia'
export const useCartStore = defineStore('cart', {
state: () => ({
cartList: []
}),
actions: {
async syncCart() {
// 根据用户token同步购物车
const res = await getCartListApi()
this.cartList = res.data
}
}
})
总结:通信方式选择指南
| 通信场景 | 推荐方式 |
|---|---|
| 父子组件基础传值 | Props + Emit |
| 父子组件方法调用 | Ref + DefineExpose |
| 父子透传非Props属性 | $attrs |
| 兄弟组件通信 | 父组件中转 / EventBus(mitt) |
| 多层级组件通信 | Provide/Inject / Pinia |
| 大型应用全局状态共享 | Pinia |
| 路由页面间传值 | Vue Router(参数/Query) |
| 插槽内容与数据联动 | 作用域插槽 |
| 组件双向绑定 | v-model |
| 通信逻辑复用 | Composition API 工具函数 |