Vue3 组件通信全解析
组件通信是 Vue 开发中绕不开的核心知识点,尤其是 Vue3 组合式 API 普及后,通信方式相比 Vue2 有了不少变化和优化。本文将抛开 TypeScript,用最通俗易懂的方式,带你梳理 Vue3 中所有常用的组件通信方式,从基础的父子通信到复杂的跨层级通信,每一种都配实战示例,新手也能轻松上手。
一、父子组件通信(最基础也最常用)
父子组件通信是日常开发中使用频率最高的场景,Vue3 为这种场景提供了清晰且高效的解决方案。
1. 父传子:Props
Props 是父组件向子组件传递数据的官方标准方式,子组件通过定义 props 接收父组件传递的值。
父组件(Parent.vue)
<template>
<div class="parent">
<h3>我是父组件</h3>
<!-- 向子组件传递数据 -->
<Child
:msg="parentMsg"
:user-info="userInfo"
:list="fruitList"
/>
</div>
</template>
<script setup>
// 引入子组件
import Child from './Child.vue'
import { ref, reactive } from 'vue'
// 定义要传递给子组件的数据
const parentMsg = ref('来自父组件的问候')
const userInfo = reactive({
name: '张三',
age: 25
})
const fruitList = ref(['苹果', '香蕉', '橙子'])
</script>
子组件(Child.vue)
<template>
<div class="child">
<h4>我是子组件</h4>
<p>父组件传递的字符串:{{ msg }}</p>
<p>父组件传递的对象:{{ userInfo.name }} - {{ userInfo.age }}岁</p>
<p>父组件传递的数组:{{ list.join('、') }}</p>
</div>
</template>
<script setup>
// 定义props接收父组件数据
const props = defineProps({
// 字符串类型
msg: {
type: String,
default: '默认值'
},
// 对象类型
userInfo: {
type: Object,
default: () => ({}) // 对象/数组默认值必须用函数返回
},
// 数组类型
list: {
type: Array,
default: () => []
}
})
// 在脚本中使用props(组合式API中可直接用props.xxx)
console.log(props.msg)
</script>
2. 子传父:自定义事件(Emits)
子组件通过触发自定义事件,将数据传递给父组件,父组件通过监听事件接收数据。
子组件(Child.vue)
<template>
<div class="child">
<h4>我是子组件</h4>
<button @click="sendToParent">向父组件传递数据</button>
</div>
</template>
<script setup>
// 声明要触发的自定义事件(可选,但推荐)
const emit = defineEmits(['childMsg', 'updateInfo'])
const sendToParent = () => {
// 触发事件并传递数据(第一个参数是事件名,后续是要传递的数据)
emit('childMsg', '来自子组件的消息')
emit('updateInfo', {
name: '李四',
age: 30
})
}
</script>
父组件(Parent.vue)
<template>
<div class="parent">
<h3>我是父组件</h3>
<!-- 监听子组件的自定义事件 -->
<Child
@childMsg="handleChildMsg"
@updateInfo="handleUpdateInfo"
/>
<p>子组件传递的消息:{{ childMsg }}</p>
<p>子组件更新的信息:{{ newUserInfo.name }} - {{ newUserInfo.age }}岁</p>
</div>
</template>
<script setup>
import Child from './Child.vue'
import { ref, reactive } from 'vue'
const childMsg = ref('')
const newUserInfo = reactive({
name: '',
age: 0
})
// 处理子组件的消息
const handleChildMsg = (msg) => {
childMsg.value = msg
}
// 处理子组件的信息更新
const handleUpdateInfo = (info) => {
newUserInfo.name = info.name
newUserInfo.age = info.age
}
</script>
二、跨层级组件通信
当组件嵌套层级较深(比如爷孙组件、跨多级组件),使用 props + emits 会非常繁琐,这时需要更高效的跨层级通信方案。
1. provide /inject(依赖注入)
provide 用于父组件(或祖先组件)提供数据,inject 用于子孙组件注入数据,支持任意层级的组件通信。
祖先组件(GrandParent.vue)
<template>
<div class="grand-parent">
<h3>我是祖先组件</h3>
<Parent />
</div>
</template>
<script setup>
import Parent from './Parent.vue'
import { ref, reactive, provide } from 'vue'
// 提供基本类型数据
const theme = ref('dark')
provide('theme', theme)
// 提供对象类型数据
const globalConfig = reactive({
fontSize: '16px',
color: '#333'
})
provide('globalConfig', globalConfig)
// 提供方法(支持双向通信)
provide('changeTheme', (newTheme) => {
theme.value = newTheme
})
</script>
孙组件(Child.vue)
<template>
<div class="child">
<h4>我是孙组件</h4>
<p>祖先组件提供的主题:{{ theme }}</p>
<p>全局配置:{{ globalConfig.fontSize }} / {{ globalConfig.color }}</p>
<button @click="changeTheme('light')">切换为亮色主题</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入祖先组件提供的数据(第二个参数是默认值)
const theme = inject('theme', 'light')
const globalConfig = inject('globalConfig', {})
const changeTheme = inject('changeTheme', () => {})
</script>
2. Vuex/Pinia(全局状态管理)
当多个不相关的组件需要共享状态,或者项目规模较大时,推荐使用官方的状态管理库,Vue3 中更推荐 Pinia(比 Vuex 更简洁)。
示例:Pinia 实现全局通信
1. 安装 Pinia
npm install pinia
2. 创建 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')
3. 创建 Store(stores/user.js)
import { defineStore } from 'pinia'
// 定义并导出store
export const useUserStore = defineStore('user', {
// 状态
state: () => ({
username: '默认用户名',
token: ''
}),
// 计算属性
getters: {
// 处理用户名格式
formatUsername: (state) => {
return `【${state.username}】`
}
},
// 方法(修改状态)
actions: {
// 更新用户信息
updateUserInfo(newInfo) {
this.username = newInfo.username
this.token = newInfo.token
},
// 清空用户信息
clearUserInfo() {
this.username = ''
this.token = ''
}
}
})
4. 组件中使用 Store
<template>
<div>
<h3>全局状态管理示例</h3>
<p>用户名:{{ userStore.formatUsername }}</p>
<p>Token:{{ userStore.token }}</p>
<button @click="updateUser">更新用户信息</button>
<button @click="clearUser">清空用户信息</button>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
// 获取store实例
const userStore = useUserStore()
// 更新用户信息
const updateUser = () => {
userStore.updateUserInfo({
username: '掘金用户',
token: '123456789'
})
}
// 清空用户信息
const clearUser = () => {
userStore.clearUserInfo()
}
</script>
三、其他常用通信方式
1. v-model 双向绑定
Vue3 中 v-model 支持自定义绑定属性,可实现父子组件的双向数据绑定,简化子传父的操作。
子组件(Child.vue)
<template>
<div class="child">
<input
type="text"
:value="modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
<!-- 支持多个v-model -->
<input
type="number"
:value="age"
@input="emit('update:age', $event.target.value)"
/>
</div>
</template>
<script setup>
defineProps(['modelValue', 'age'])
const emit = defineEmits(['update:modelValue', 'update:age'])
</script>
父组件(Parent.vue)
<template>
<div class="parent">
<h3>父组件</h3>
<Child
v-model="username"
v-model:age="userAge"
/>
<p>用户名:{{ username }}</p>
<p>年龄:{{ userAge }}</p>
</div>
</template>
<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const username = ref('')
const userAge = ref(0)
</script>
2. 事件总线(mitt)
Vue3 移除了 Vue2 的 $on/$emit 事件总线,可使用第三方库 mitt 实现任意组件间的通信。
1. 安装 mitt
npm install mitt
2. 创建事件总线(utils/bus.js)
import mitt from 'mitt'
const bus = mitt()
export default bus
3. 组件 A 发送事件
<template>
<div>
<button @click="sendMsg">发送消息到组件B</button>
</div>
</template>
<script setup>
import bus from '@/utils/bus'
const sendMsg = () => {
// 触发自定义事件并传递数据
bus.emit('msgEvent', '来自组件A的消息')
}
</script>
4. 组件 B 接收事件
<template>
<div>
<p>组件A传递的消息:{{ msg }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import bus from '@/utils/bus'
const msg = ref('')
// 挂载时监听事件
onMounted(() => {
bus.on('msgEvent', (data) => {
msg.value = data
})
})
// 卸载时移除监听(避免内存泄漏)
onUnmounted(() => {
bus.off('msgEvent')
})
</script>
四、通信方式选型建议
表格
| 通信场景 | 推荐方式 |
|---|---|
| 父传子 | Props |
| 子传父 | 自定义事件(Emits)/v-model |
| 爷孙 / 跨层级 | provide / inject |
| 全局共享状态 | Pinia |
| 任意组件临时通信 | mitt 事件总线 |
总结
- Vue3 中父子组件通信优先使用 Props + Emits,v-model 可简化双向绑定场景;
- 跨层级通信推荐 provide / inject,全局状态管理首选 Pinia;
- 临时的任意组件通信可使用 mitt 事件总线,注意及时移除监听避免内存泄漏。
组件通信的核心是 “数据流向清晰”,无论选择哪种方式,都要保证数据的传递路径可追溯,避免滥用全局通信导致代码维护困难。希望本文能帮助你彻底掌握 Vue3 组件通信,少走弯路~