Vue 中组件通信有哪些方式?该用哪个?
2026/7/30 10:16:34 网站建设 项目流程

Vue 中组件通信有哪些方式?该用哪个?

一句话总结:父子用 props/emit,跨级用 provide/inject,兄弟用状态提升或 EventBus,全局用 Pinia。按关系选方案,别上来就 Vuex/Pinia!


正文目录

  1. Vue 组件通信全景图
  2. 5 大高频场景 & 最佳实践
  3. 万能兜底:按关系选方案
  4. 预防 checklist(不再踩坑)
  5. 一句话总结

一、Vue 组件通信全景图

Vue 组件通信的本质就是:数据怎么从 A 传到 B,B 怎么通知 A

按组件关系分类:

关系推荐方案一句话
父 → 子props父亲给孩子东西
子 → 父emit孩子喊一声通知父亲
父 → 子(直接操作)ref + expose父亲直接伸手
祖先 → 后代provide / inject祖宗传家宝,后代直接拿
兄弟之间状态提升 / EventBus找共同的父亲中转
任意组件Pinia全局仓库,谁都能取

常见误区:上来就 Pinia,简单父子通信也套全局状态,代码复杂度暴增。


二、5 大高频场景 & 最佳实践

① 父子通信:props 传了对象,子组件直接改
<!-- ❌ 子组件直接修改 props 对象的属性 --> <script setup> const props = defineProps(['user']); const changeName = () => { props.user.name = 'new'; // ❌ 虽然能改,但数据流不清晰 }; </script>

修复:子组件 emit,父组件改

<!-- ✅ 子组件 emit 事件,父组件修改数据 --> <script setup> const props = defineProps(['user']); const emit = defineEmits(['update']); const changeName = () => { emit('update', { ...props.user, name: 'new' }); // ✅ emit 新对象 }; </script>
② 跨级通信:层层透传 props(prop drilling)
<!-- ❌ 中间组件不需要这个 prop,只是透传 --> <!-- Grandfather.vue --> <Template :theme="theme" /> <!-- Father.vue (不需要 theme,但必须接) --> <script setup> defineProps(['theme']); // ❌ 纯透传 </script> <template> <Child :theme="theme" /> </template> <!-- Child.vue --> <script setup> defineProps(['theme']); </script>

修复:用 provide/inject

<!-- ✅ 祖先组件 provide --> <!-- Grandfather.vue --> <script setup> import { ref, provide } from 'vue'; const theme = ref('dark'); provide('theme', theme); // ✅ 注入 </script> <template> <Father /> </template>
<!-- ✅ 后代组件 inject,中间层完全不需要知道 --> <!-- Child.vue --> <script setup> import { inject } from 'vue'; const theme = inject('theme'); // ✅ 直接拿 </script>
③ 兄弟通信:用 EventBus 但忘记销毁
// ❌ EventBus 方案,组件销毁时没移除监听 → 内存泄漏importmittfrom'mitt';constbus=mitt();// BrotherA.vuebus.emit('refresh',data);// BrotherB.vuebus.on('refresh',handler);// ❌ 没有 bus.off,切走后还在监听

修复方案一:onUnmounted 时移除监听

// ✅ 组件销毁时移除import{onUnmounted}from'vue';bus.on('refresh',handler);onUnmounted(()=>{bus.off('refresh',handler);// ✅ 清理});

修复方案二(推荐):状态提升到共同父组件

<!-- ✅ 共同父组件管理状态,用 props/emit 传给两个子组件 --> <script setup> import { ref } from 'vue'; const sharedData = ref(null); const updateData = (val) => { sharedData.value = val; }; </script> <template> <BrotherA :data="sharedData" @update="updateData" /> <BrotherB :data="sharedData" @update="updateData" /> </template>
④ 父组件直接调子组件方法:用 ref 但子组件没 expose
<!-- ❌ 父组件拿到 ref,但调不到子组件方法 --> <script setup> import { ref } from 'vue'; const childRef = ref(null); const callChild = () => { childRef.value?.doSomething(); // ❌ 子组件没 expose,拿到的是 undefined }; </script> <template> <Child ref="childRef" /> </template>
<!-- 子组件 --> <script setup> const doSomething = () => { /* ... */ }; // ❌ 没有 defineExpose,外部访问不到 </script>

修复:子组件用 defineExpose

<!-- ✅ 子组件暴露方法 --> <script setup> const doSomething = () => { /* ... */ }; const reset = () => { /* ... */ }; defineExpose({ doSomething, reset }); // ✅ 主动暴露 </script>
⑤ 全局状态:该用 Pinia 却用了 provide/inject
<!-- ❌ 全局用户信息用 provide/inject,刷新丢失、无法持久化 --> <!-- App.vue --> <script setup> import { ref, provide } from 'vue'; const user = ref(null); provide('user', user); // ❌ 全局状态不该用 provide,没法跨路由管理 </script>

修复:用 Pinia

// ✅ stores/user.jsimport{defineStore}from'pinia';import{ref}from'vue';exportconstuseUserStore=defineStore('user',()=>{constuser=ref(null);constsetUser=(val)=>{user.value=val;};constlogout=()=>{user.value=null;};return{user,setUser,logout};});
<!-- ✅ 任意组件直接用 --> <script setup> import { useUserStore } from '@/stores/user'; const userStore = useUserStore(); // userStore.user, userStore.setUser(), userStore.logout() </script>

三、万能兜底:按关系选方案

组件关系首选方案备选方案
父 → 子propsref + expose
子 → 父emit
祖先 → 后代provide/injectPinia
兄弟状态提升EventBus / Pinia
任意(全局)Pinia
临时事件总线mittPinia(如果状态多了就迁移)

决策流程图

需要通信的组件关系是什么? ├── 父子 → props + emit ├── 祖先后代 → provide/inject ├── 兄弟 → 状态提升到共同父级 │ └── 父级太远?→ Pinia └── 任意组件 → Pinia └── 只是临时事件?→ mitt EventBus(记得 off)

四、预防 checklist

  • 父子通信优先props + emit,不要用 ref 直接操作子组件(除非表单聚焦等场景)
  • 跨级传 prop超过 2 层就换provide/inject
  • 兄弟通信先考虑状态提升,层级太深再上Pinia
  • 用 EventBus必须在 onUnmounted 里 off,否则内存泄漏
  • 父组件 ref 调子组件方法,子组件必须 defineExpose
  • 全局状态(用户信息、主题、权限)用 Pinia,不要用 provide/inject
  • provide 传响应式数据时传 ref/computed,别传普通值
  • inject 时给默认值inject('key', defaultValue),防止找不到 provider

五、一句话总结

「组件通信按关系选方案」:父子 props/emit,跨级 provide/inject,兄弟状态提升,全局 Pinia。别简单通信套全局状态,也别复杂状态硬透传 props。选对工具,数据流才清晰!


最后问候亲爱的朋友们,并邀请你们阅读我的全新著作

📚 《Vue.js 3企业级项目开发实战(微课视频版》

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询