Vue 3 组件通信别只会用 Props 和 Emits 了你想知道的都在这里作为前端开发工程师组件通信是Vue开发中绕不开的话题。虽然Props和Emits是最基础的通信方式但在实际项目中我们经常会遇到更复杂的场景。今天我将带你全面了解Vue 3中14种组件通信方式让你的开发技能更上一层楼 目录父子组件通信6种方式兄弟组件通信3种方式跨层级通信2种方式全局通信2种方式特殊场景通信3种方式完整对比总结表一、父子组件通信6种方式1️⃣ Props父 → 子最基础的父传子方式遵循单向数据流原则。!-- 父组件 -- template Child :msgparentMsg :countcount / /template script setup import { ref } from vue import Child from ./Child.vue const parentMsg ref(父组件数据) const count ref(10) /script!-- 子组件 -- script setup const props defineProps({ msg: String, count: Number }) /script适用场景简单的父子数据传递2️⃣ Emit子 → 父子组件向父组件传递数据或触发事件。!-- 子组件 -- template button clickhandleClick点击我/button /template script setup const emit defineEmits([updateData, customEvent]) const handleClick () { emit(updateData, 子组件数据) emit(customEvent, { id: 1, name: test }) } /script!-- 父组件 -- template Child update-datahandleUpdate custom-eventhandleCustom / /template script setup const handleUpdate (data) { console.log(收到子组件数据:, data) } const handleCustom (payload) { console.log(自定义事件:, payload) } /script适用场景子组件需要通知父组件状态变化3️⃣ v-model双向绑定Vue 3中v-model的语法糖实现父子组件数据双向绑定。单值绑定Vue 3.4!-- 子组件 -- script setup const model defineModel() /script template input v-modelmodel / /template!-- 父组件 -- template Child v-modelparentValue / /template script setup import { ref } from vue const parentValue ref(初始值) /script多个v-model绑定!-- 子组件 -- script setup const props defineProps({ modelValue: String, firstName: String, lastName: String }) const emit defineEmits([update:modelValue, update:firstName, update:lastName]) /script!-- 父组件 -- template Child v-modelfullName v-model:first-namefirstName v-model:last-namelastName / /template适用场景表单组件、需要双向数据同步的场景4️⃣ ref defineExpose父调用子组件方法父组件直接调用子组件的方法或访问子组件数据。!-- 子组件 -- script setup import { ref } from vue const childData ref(子组件数据) const childMethod () { console.log(子组件方法被调用) } // 必须暴露才能被父组件访问 defineExpose({ childData, childMethod }) /script!-- 父组件 -- template Child refchildRef / button clickcallChildMethod调用子组件方法/button /template script setup import { ref, onMounted } from vue import Child from ./Child.vue const childRef ref(null) const callChildMethod () { childRef.value.childMethod() console.log(子组件数据:, childRef.value.childData) } onMounted(() { // 组件挂载后即可访问 console.log(子组件实例:, childRef.value) }) /script适用场景父组件需要直接操作子组件内部方法或数据5️⃣ $attrs属性透传透传父组件传递的、但子组件未声明的属性到更深层组件。!-- 父组件 -- template Child classcustom-class stylecolor: red data-id123 clickhandleClick / /template!-- 子组件 -- template !-- 透传所有未声明的属性到内部元素 -- div input v-bind$attrs / /div /template script setup // 不需要声明props$attrs会自动包含所有未声明的属性 /script适用场景封装第三方组件库高阶组件开发多层组件嵌套中的属性传递6️⃣ 作用域插槽子传父数据子组件向父组件传递数据由父组件决定如何渲染。!-- 子组件 -- template div slot :useruser :logoutlogout/slot /div /template script setup import { ref } from vue const user ref({ name: 张三, age: 25 }) const logout () { console.log(退出登录) } /script!-- 父组件 -- template Child v-slot{ user, logout } div p用户名: {{ user.name }}/p p年龄: {{ user.age }}/p button clicklogout退出/button /div /Child !-- 简写语法 -- Child #default{ user } p{{ user.name }}/p /Child /template适用场景列表组件如Table、List需要父组件自定义渲染逻辑的场景二、兄弟组件通信3种方式7️⃣ 共同父组件中转通过父组件作为桥梁实现兄弟组件通信。!-- 父组件 -- template BrotherA data-changehandleDataChange / BrotherB :sharedDatasharedData / /template script setup import { ref } from vue import BrotherA from ./BrotherA.vue import BrotherB from ./BrotherB.vue const sharedData ref() const handleDataChange (data) { sharedData.value data } /script适用场景兄弟组件数量较少逻辑简单8️⃣ mitt事件总线轻量级的事件发布/订阅库实现任意组件通信。安装和配置npm install mitt// eventBus.js import mitt from mitt export const emitter mitt()使用示例!-- 组件A发送方 -- script setup import { emitter } from /utils/eventBus const sendData () { emitter.emit(data-update, { id: 1, value: 新数据 }) } /script!-- 组件B接收方 -- script setup import { emitter } from /utils/eventBus import { onUnmounted } from vue const handleDataUpdate (data) { console.log(收到数据:, data) } // 监听事件 emitter.on(data-update, handleDataUpdate) // 组件卸载时移除监听避免内存泄漏 onUnmounted(() { emitter.off(data-update, handleDataUpdate) }) /script适用场景跨层级组件通信不想引入完整状态管理的轻量级场景临时性通信需求9️⃣ Pinia状态管理Vue官方推荐的状态管理库替代Vuex。安装和配置npm install pinia// main.js import { createApp } from vue import { createPinia } from pinia import App from ./App.vue const app createApp(App) const pinia createPinia() app.use(pinia) app.mount(#app)创建Store// stores/userStore.js import { defineStore } from pinia import { ref, computed } from vue export const useUserStore defineStore(user, () { // state const userInfo ref({ name: , age: 0 }) const token ref() // getters const isLogin computed(() !!token.value) // actions const setUserInfo (data) { userInfo.value data } const login async (username, password) { // 模拟登录 token.value xxx-token userInfo.value { name: username, age: 25 } } const logout () { token.value userInfo.value { name: , age: 0 } } return { userInfo, token, isLogin, setUserInfo, login, logout } })组件中使用script setup import { useUserStore } from /stores/userStore import { storeToRefs } from pinia const userStore useUserStore() // 解构保持响应式 const { userInfo, isLogin } storeToRefs(userStore) // 调用action const handleLogin () { userStore.login(zhangsan, 123456) } const handleLogout () { userStore.logout() } /script适用场景全局状态共享用户信息、购物车等复杂业务逻辑的状态管理需要持久化或服务端同步的场景三、跨层级通信2种方式 provide / inject依赖注入祖先组件向任意后代组件注入数据无需逐层传递。!-- 祖先组件App.vue -- script setup import { provide, ref } from vue const theme ref(dark) const userInfo ref({ name: 张三, role: admin }) // 提供数据 provide(themeConfig, theme) provide(userInfo, userInfo) // 提供方法 provide(updateTheme, (newTheme) { theme.value newTheme }) /script!-- 中间层组件不需要参与传递 -- template div GrandChild / /div /template!-- 后代组件 -- script setup import { inject } from vue // 注入数据 const theme inject(themeConfig) const userInfo inject(userInfo) // 注入方法 const updateTheme inject(updateTheme) const changeTheme () { updateTheme(light) } /script适用场景主题配置、语言设置等全局配置深层嵌套组件的数据共享避免props drilling属性逐层传递1️⃣1️⃣ $root访问根实例通过根实例进行全局通信不推荐仅作了解。// 在任何组件中 const app getCurrentInstance().appContext.app app.config.globalProperties.$globalData 全局数据注意Vue 3中不推荐使用此方式建议使用provide/inject或Pinia。四、全局通信2种方式1️⃣2️⃣ localStorage / sessionStorage利用浏览器本地存储实现组件间通信。// 存储数据 localStorage.setItem(userData, JSON.stringify({ name: 张三 })) // 读取数据 const userData JSON.parse(localStorage.getItem(userData)) // 监听storage变化 window.addEventListener(storage, (e) { if (e.key userData) { console.log(数据变化:, e.newValue) } })适用场景需要持久化的数据跨页面/标签页通信离线数据存储1️⃣3️⃣ app.config.globalProperties在应用级别添加全局属性。// main.js const app createApp(App) app.config.globalProperties.$api { getUser: () { /* ... */ }, saveData: () { /* ... */ } } app.mount(#app)!-- 在任何组件中 -- script setup import { getCurrentInstance } from vue const { proxy } getCurrentInstance() proxy.$api.getUser() /script适用场景全局工具函数第三方库实例如axios全局配置五、特殊场景通信3种方式1️⃣4️⃣ Teleport传送门将组件渲染到DOM树的其他位置。template div h1主内容/h1 !-- 将模态框渲染到body下 -- Teleport tobody div classmodal p这是一个模态框/p /div /Teleport /div /template适用场景模态框、弹窗需要脱离父组件样式影响的组件固定定位组件1️⃣5️⃣ 动态组件通信使用component :is动态切换组件时的通信。template component :iscurrentComponent :datasharedData updatehandleUpdate / /template script setup import { ref, computed } from vue import ComponentA from ./ComponentA.vue import ComponentB from ./ComponentB.vue const currentComponent ref(ComponentA) const sharedData ref({}) const components { ComponentA, ComponentB } const handleUpdate (data) { sharedData.value data } /script适用场景选项卡切换步骤向导动态表单1️⃣6️⃣ 自定义事件修饰符为自定义组件添加v-model修饰符支持。!-- 子组件 -- script setup const props defineProps({ modelValue: String }) const emit defineEmits([update:modelValue]) const handleInput (e) { let value e.target.value // 处理修饰符 if (props.modelModifiers?.trim) { value value.trim() } if (props.modelModifiers?.capitalize) { value value.charAt(0).toUpperCase() value.slice(1) } emit(update:modelValue, value) } /script template input :valuemodelValue inputhandleInput / /template!-- 父组件 -- template !-- 支持修饰符 -- CustomInput v-model.trim.capitalizevalue / /template 完整对比总结表通信方式适用场景优点缺点推荐指数Props父→子简单、官方推荐、类型安全只能父子使用⭐⭐⭐⭐⭐Emit子→父简单、符合单向数据流只能父子使用⭐⭐⭐⭐⭐v-model双向绑定语法简洁、语义清晰需要配合props/emits⭐⭐⭐⭐refexpose父调子方法直观、灵活只能父对子⭐⭐⭐⭐$attrs属性透传避免中间层冗余代码可读性稍差⭐⭐⭐作用域插槽子传父数据灵活、父组件控制渲染语法稍复杂⭐⭐⭐⭐mitt任意组件轻量、解耦需手动管理订阅⭐⭐⭐⭐Pinia全局状态集中管理、类型安全学习成本⭐⭐⭐⭐⭐provide/inject跨层级避免props drilling松散耦合⭐⭐⭐⭐localStorage持久化跨页面、持久化同步问题⭐⭐⭐TeleportDOM位置灵活渲染位置特定场景⭐⭐⭐ 实战建议选择通信方式的原则优先使用Props/Emit简单场景首选符合Vue设计哲学避免过度使用全局状态不是所有数据都需要放入Pinia考虑组件复用性选择通用性更强的通信方式关注性能避免不必要的响应式数据传递常见误区❌滥用provide/inject不要用它替代props它更适合配置类数据❌过度依赖事件总线mitt适合轻量级场景复杂业务用Pinia❌忘记清理事件监听使用mitt时记得在onUnmounted中移除监听❌暴露过多子组件内部defineExpose只暴露必要的API 总结Vue 3提供了丰富的组件通信方式每种方式都有其适用场景简单父子通信Props Emit双向绑定v-model父调子方法ref defineExpose跨层级通信provide/inject全局状态Pinia轻量级任意通信mitt灵活渲染作用域插槽掌握这些通信方式能让你在面对不同业务场景时游刃有余。记住没有最好的方式只有最合适的方式互动话题你在项目中遇到过哪些复杂的组件通信场景是如何解决的欢迎在评论区分享你的经验本文基于Vue 3.4版本编写代码示例均经过实际测试。如有疑问欢迎交流讨论