Vue3中组件间通信详解
Vue3中组件间通信详解一、 父子组件通信最基础、最高频1. Props父传子2. Emits子传父3. v-model / defineModel双向绑定4. Ref DefineExpose父调用子方法二、 跨层级/祖孙组件通信1. Provide / Inject三、 兄弟组件/任意组件通信1. Mitt 事件总线四、 全局状态管理1.Pinia (推荐)五、 其他特殊场景1. Slots插槽内容分发与定制场景 A默认插槽与具名插槽场景 B作用域插槽Scoped Slots2. $attrs属性透传Attribute Inheritance场景 A禁用默认继承并手动控制绑定位置场景 B多层组件透传高阶组件六、总结与选型建议在 Vue3 开发中组件通信是构建复杂应用的核心技能。根据组件之间的关系父子、兄弟、跨层级、全局Vue3 提供了多种高效且规范的通信方案。一、 父子组件通信最基础、最高频父子组件通信遵循单向数据流原则父组件通过属性向下传递数据子组件通过事件向上传递消息。1. Props父传子这是最基础的通信方式数据由父组件流向子组件子组件只能读取不能直接修改。父组件通过属性绑定传递数据。子组件使用defineProps接收数据。支持类型校验、默认值和必填项设置。!--父组件 Father.vue--templateChild:msgmessage:count10//templatescript setupimport{ref}fromvueimportChildfrom./Child.vueconstmessageref(Hello Vue3)/script!--子组件 Child.vue--script setup// 接收 props支持类型校验constpropsdefineProps({msg:{type:String,required:true},count:{type:Number,default:0}})// 使用 props.msg/script2. Emits子传父当子组件需要通知父组件更新数据或执行操作时使用自定义事件。子组件使用defineEmits定义事件并通过emit触发。父组件监听子组件触发的事件并处理。!--子组件 Child.vue--templatebutton clickhandleClick发送数据给父组件/button/templatescript setupconstemitdefineEmits([update-data])consthandleClick(){emit(update-data,来自子组件的数据)}/script!--父组件 Father.vue--templateChild update-datareceiveData//templatescript setupconstreceiveData(payload){console.log(收到:,payload)}/script3. v-model / defineModel双向绑定用于实现父子组件数据的双向同步。Vue 3.4 推荐使用defineModel宏简化写法。原理本质上是propsemit(update:xxx)的语法糖。用法父组件使用v-model子组件使用defineModel。!--父组件--templateChild v-model:titlepageTitle//template!--子组件 Child.vue(Vue3.4)--script setupconsttitledefineModel(title)// 直接修改 title.value 即可同步到父组件/script4. Ref DefineExpose父调用子方法当父组件需要直接调用子组件内部的方法或访问其状态时使用。注意这会破坏组件封装性应谨慎使用。子组件使用defineExpose暴露需要被访问的方法或属性。父组件通过ref获取子组件实例。!--子组件 Child.vue--script setupconstsayHi()console.log(Hi!)// 暴露方法defineExpose({sayHi})/script!--父组件 Father.vue--templateChild refchildRef/button clickcallChild调用子组件方法/button/templatescript setupimport{ref}fromvueconstchildRefref(null)constcallChild(){childRef.value?.sayHi()}/script二、 跨层级/祖孙组件通信当组件嵌套过深如 A - B - C - D使用 Props 逐层传递Prop Drilling非常繁琐此时可采用以下方案1. Provide / Inject祖先组件提供数据后代组件无论嵌套多深都可以注入使用。适合主题配置、用户信息等深层共享数据。祖先组件使用provide提供数据。后代组件使用inject注入数据。!--祖先组件--script setupimport{provide,ref}fromvueconstthemeref(dark)provide(theme-key,theme)/script!--深层后代组件--script setupimport{inject}fromvueconstthemeinject(theme-key)/script三、 兄弟组件/任意组件通信1. Mitt 事件总线Vue3 移除了原生的$bus推荐使用轻量级第三方库mitt实现发布-订阅模式。适合非父子、非全局状态的简单通信。注意需在组件卸载时手动移除监听防止内存泄漏。// utils/bus.jsimportmittfrommittexportconstemittermitt()!--组件A(发送)--script setupimport{emitter}from/utils/busconstsend(){emitter.emit(my-event,data)}/script!--组件B(接收)--script setupimport{onMounted,onUnmounted}fromvueimport{emitter}from/utils/busconsthandler(data)console.log(data)onMounted((){emitter.on(my-event,handler)})onUnmounted((){emitter.off(my-event,handler)// 务必清理})/script四、 全局状态管理对于大型项目涉及多个组件共享复杂状态如登录态、购物车、权限等建议使用状态管理库。1.Pinia (推荐)Pinia 是 Vue 官方推荐的状态管理库替代了 Vuex。它去除了 Mutation仅保留 State、Getter、Action对 TypeScript 支持更好且体积更小。特点模块化设计无需嵌套模块调试方便。用法创建 Store在组件中直接调用 store 的状态和方法。// stores/user.jsimport{defineStore}frompiniaimport{ref}fromvueexportconstuseUserStoredefineStore(user,(){constnameref(Admin)functionsetName(newName){name.valuenewName}return{name,setName}})!--任意组件--script setupimport{useUserStore}from/stores/userconstuserStoreuseUserStore()// 直接使用 userStore.name 或 userStore.setName()/script五、 其他特殊场景1. Slots插槽内容分发与定制插槽允许父组件向子组件传递模板结构实现“结构由子组件定内容由父组件定”的解耦。场景 A默认插槽与具名插槽适用于布局组件如卡片、弹窗其中部分区域固定部分区域需自定义。子组件Card.vuetemplatedivclasscard!--具名插槽头部--headerclasscard-headerslot nameheader默认标题/slot/header!--默认插槽主体内容--mainclasscard-bodyslot默认主体内容/slot/main!--具名插槽底部操作区--footerclasscard-footerslot namefooterbutton默认按钮/button/slot/footer/div/templatestyle scoped.card{border:1px solid ddd;padding:10px;}.card-header{font-weight:bold;margin-bottom:10px;}.card-footer{margin-top:10px;text-align:right;}/style父组件Parent.vue使用templateCard!--1.具名插槽写法v-slot:name 或简写 name--template headerh2自定义标题/h2/template!--2.默认插槽直接写内容无需 template 包裹也可用default--p这是父组件传入的动态正文内容。/p!--3.具名插槽底部--template footerbutton clicksave保存/buttonbutton clickcancel取消/button/template/Card/templatescript setupimportCardfrom./Card.vueconstsave()console.log(保存)constcancel()console.log(取消)/script场景 B作用域插槽Scoped Slots适用于列表、表格等组件子组件提供数据父组件决定如何渲染这些数据。子组件UserList.vuetemplateulli v-foruser in users:keyuser.id!--通过:useruser将数据暴露给父组件 父组件接收时可以使用任意变量名如 slotProps--slot:useruser:indexuser.id!--默认渲染方式--{{user.name}}/slot/li/ul/templatescript setupimport{ref}fromvueconstusersref([{id:1,name:Alice,role:Admin},{id:2,name:Bob,role:User}])/script父组件Parent.vue使用templateUserList!--v-slot:defaultslotPropsslotProps 包含子组件暴露的所有属性:{user,index}--templatedefault{ user, index }divclasscustom-itemspan{{index}}./spanstrong{{user.name}}/strongspan v-ifuser.role Adminclassbadge管理员/span/div/template/UserList/templatescript setupimportUserListfrom./UserList.vue/scriptstyle scoped.badge{background:red;color:white;padding:2px 5px;font-size:12px;}/style2. $attrs属性透传Attribute Inheritance$attrs包含父组件传递给子组件但未被声明为 props 且未被监听为 emits 的所有属性包括 class, style, 事件监听器等。常用于高阶组件或封装第三方库组件。场景 A禁用默认继承并手动控制绑定位置默认情况下未声明的属性会自动绑定到组件的根元素。如果希望绑定到内部特定元素如 input需禁用默认继承。子组件CustomInput.vuetemplatedivclassinput-wrapperlabel标签/label!--1.defineOptions({inheritAttrs:false})禁用了自动绑定到根 div2.v-bind$attrs手动将属性绑定到 input 上 这样父组件传的 placeholder,focus,class等都会生效在 input 上--input v-bind$attrs//div/templatescript setup// Vue 3.3 推荐写法defineOptions({inheritAttrs:false})// 注意这里没有定义任何 props所以所有传入属性都会进入 $attrs/script父组件Parent.vue使用template!--placeholder,focus,class均未在子组件 props 中声明 它们会通过 $attrs 透传到内部的 input 元素上--CustomInput placeholder请输入内容focusonFocusclasscustom-style//templatescript setupimportCustomInputfrom./CustomInput.vueconstonFocus()console.log(输入框获得焦点)/script场景 B多层组件透传高阶组件当组件只是作为中间层需要将属性继续传递给更深层的子组件时。中间组件Wrapper.vuetemplatedivclasswrapper-border!--将接收到的所有未声明属性透传给 BaseButton--BaseButton v-bind$attrs!--如果需要透传插槽也可以显式写出--slot/slot/BaseButton/div/templatescript setupimportBaseButtonfrom./BaseButton.vue// Wrapper 不声明任何 props所有属性直接透传/script底层组件BaseButton.vuetemplatebutton v-bind$attrsslot/slot/button/templatescript setup// 同样不声明 props确保属性最终绑定到 button 元素defineOptions({inheritAttrs:false})/script父组件使用template!--type,disabled,click 等属性会穿过 Wrapper最终绑定到 BaseButton 的 button 标签上--Wrapper typesubmitdisabled clickhandleSubmit提交/Wrapper/template六、总结与选型建议场景推荐方案备注父传子props单向数据流最基础子传父emits保持单向流解耦父子双向同步v-model/defineModel表单、输入框常用父调用子方法refdefineExpose慎用破坏封装跨多层级传递provide/inject避免 Prop Drilling兄弟/任意组件Mitt事件总线轻量级需手动清理监听大型全局状态Pinia官方推荐替代 Vuex内容布局定制Slots特别是作用域插槽在实际开发中应优先使用 Props/Emits 和 Pinia仅在特定场景下使用 Provide/Inject 或 Mitt以保持代码的可维护性和清晰的数据流向。