十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

八、Vue组件通信详解

八、Vue组件通信详解 一、vue2组件通信汇总表通信方式适用场景/范围通信方向核心原理与特点Props / $ emit父子组件父传子 / 子传父最核心的单向数据流。父传子用props子传父通过this.$emit触发自定义事件。v-model父子组件双向绑定语法糖。本质是props接收value子组件通过$emit(input, val)回传数据。.sync 修饰符父子组件双向绑定语法糖。用于子组件修改父组件的某个状态本质是props接收属性子组件触发update:属性名事件。$ refs父子组件父访问子父组件通过ref属性获取子组件实例可直接调用子组件的方法或读取数据强耦合慎用。Event Bus任意组件双向全局事件总线。创建一个空的 Vue 实例作为中转站通过$emit发送$on接收。共同父组件中转兄弟组件子→父→子将共享状态提升到父组件通过父组件的props和$emit分别和两个子组件通信。Provide / Inject祖孙组件祖先→后代依赖注入。祖先组件通过provide提供数据后代组件通过inject注入Vue2中默认非响应式。$ attrs / $ listeners跨层级组件祖先→后代跨代透传。$attrs传递未声明的属性$listeners传递事件监听器常用于高阶组件封装。$ parent / $ children父子组件双向访问直接访问父/子组件实例。极易导致代码脆弱和强耦合官方强烈建议谨慎使用。Vuex全局任意组件集中式状态管理官方状态管理库。将公共数据抽离为全局变量通过commit和dispatch进行读写适合中大型项目。Slot (插槽)父子组件父→子内容分发父组件向子组件传递模板内容。作用域插槽可实现子组件向父组件回传数据。路由传参页面间通信单向通过 Vue Router 的query、params或state在页面跳转时传递参数。二、vue3组件通信汇总表通信方式适用场景/范围通信方向核心原理与特点Props / Emits父子组件父传子 / 子传父核心通信。Vue3 推荐使用defineProps接收数据defineEmits声明并触发事件。v-model / defineModel父子组件双向绑定语法糖升级。Vue3.4 推荐使用defineModel宏无需再手动定义 props 和 emits实现极简双向绑定。Ref DefineExpose父子组件父访问子替代了部分$refs场景。子组件必须通过defineExpose显式暴露属性和方法父组件才能通过ref访问。$ attrs跨层级组件祖先→后代属性与事件合并透传。Vue3 将$listeners合并到了$attrs中只需v-bind$attrs即可透传所有未声明属性和事件。Provide / Inject祖孙组件祖先→后代依赖注入升级。在script setup中使用provide和inject函数且原生支持响应式直接传 ref/reactive 即可。Mitt 事件总线任意组件双向替代了 Vue2 的 Event Bus。由于 Vue3 移除了$on/$off官方推荐使用轻量级第三方库mitt实现全局事件通信。Pinia全局任意组件集中式状态管理Vue3 官方首推状态管理库替代 Vuex。去除了繁琐的mutations完美支持 TypeScriptAPI 更简洁。Teleport特殊场景DOM 渲染位置Vue3 新增。允许将组件的模板渲染到 DOM 中的其他位置如body常用于弹窗、下拉菜单等。共同父组件中转兄弟组件子→父→子与 Vue2 原理一致。将共享状态提升到父组件通过props和emits分别和两个子组件通信。路由传参页面间通信单向与 Vue2 原理一致。通过 Vue Router 的query、params或state传递参数。浏览器缓存跨页面持久化单向利用localStorage、sessionStorage或window对象在组件间共享或持久化数据。三、vue2组件通信详解Vue2 提供了多种组件通信方式适用于不同的场景和需求。合理选择通信方式可以让组件间的数据流动更加清晰、可维护。1. Props / $emit最核心、最推荐传值范围Props父传子$emit子传父适用场景父子组件之间的直接通信是最基础、最常用的通信方式。子组件 (Child.vue)template div !-- 2. 渲染父组件传过来的数据 -- p接收到的消息{{ msg }}/p !-- 4. 点击按钮触发事件并把数据传回给父组件 -- button click$emit(change, 我是子组件传回的数据)通知父组件/button /div /template script export default { // 1. 声明接收父组件的数据 props: [msg] } /script父组件 (Parent.vue)template div !-- 3. 把数据传给子组件并监听子组件的事件 -- Child :msgparentMsg changehandleChildEvent / /div /template script export default { data() { return { parentMsg: 我是父组件的数据 } }, methods: { // 5. 接收子组件传过来的数据 handleChildEvent(data) { console.log(data); // 输出: 我是子组件传回的数据 } } } /script核心要点父组件通过v-bind或:将数据传递给子组件子组件通过props声明接收的数据子组件通过$emit触发自定义事件将数据传回父组件数据流是单向的父 → 子props子 → 父事件2. v-model双向绑定语法糖传值范围父子双向通信父传子 value子传父 input 事件适用场景表单元素或需要双向绑定的自定义组件。子组件 (Child.vue)template div !-- 2. 绑定接收到的 value 属性并在输入时触发 input 事件 -- input :valuevalue input$emit(input, $event.target.value) / /div /template script export default { // 1. 默认接收 value 属性 props: [value] } /script父组件 (Parent.vue)template div p父组件的值{{ text }}/p !-- 3. 使用 v-model实现父子数据双向同步 -- Child v-modeltext / /div /template script export default { data() { return { text: } } } /scriptv-model 原理写法等价写法说明Child v-modeltext /Child :valuetext inputtext $event /默认绑定 value 属性和 input 事件v-model.trimtext:valuetext inputtext $event.trim()带修饰符的 v-model自定义 v-model在子组件中通过model选项可以修改默认的 prop 和事件名。exportdefault{model:{prop:checked,// 修改默认的 value 属性名event:change// 修改默认的 input 事件名},props:[checked]}3. .sync 修饰符Vue2 特有传值范围父子双向通信父传子 属性子传父 update:属性 事件适用场景需要双向绑定的多个 propsVue3 中已移除改用多个 v-model 实现。子组件 (Child.vue)template div v-ifvisible p这是一个弹窗/p !-- 2. 触发 update:visible 事件修改父组件的值 -- button click$emit(update:visible, false)关闭弹窗/button /div /template script export default { // 1. 接收父组件传过来的状态 props: [visible] } /script父组件 (Parent.vue)template div button clickisShow true打开弹窗/button !-- 3. 使用 .sync子组件修改时会自动同步到 isShow -- Child :visible.syncisShow / /div /template script export default { data() { return { isShow: false } } } /script.sync 修饰符原理语法糖写法等价写法说明:visible.syncisShow:visibleisShow update:visibleisShow $event单个属性双向同步支持多个属性:width.syncw :height.synch多个属性分别同步4. $refs父组件直接访问子组件传值范围父组件单向访问子组件直接获取子组件实例适用场景父组件需要直接调用子组件的方法或访问其数据如表单校验。子组件 (Child.vue)template input v-modelinputValue placeholder请输入内容 / /template script export default { data() { return { inputValue: } }, methods: { // 2. 暴露给父组件调用的方法 validate() { if (!this.inputValue) { alert(内容不能为空); return false; } return true; } } } /script父组件 (Parent.vue)template div !-- 1. 给子组件打上 ref 标记 -- Child refmyForm / button clickhandleSubmit提交/button /div /template script export default { methods: { handleSubmit() { // 3. 通过 $refs 直接调用子组件内部的方法 const isValid this.$refs.myForm.validate(); if (isValid) { console.log(验证通过); } } } } /script使用注意事项$refs只在组件渲染完成后才填充应在mounted及之后的生命周期中访问过度使用$refs会破坏组件封装性应优先使用 props 和事件不要在使用v-for的循环中依赖$refs的顺序5. Event Bus全局事件总线传值范围任意组件间通信常用于兄弟组件、无直接关系的组件适用场景非父子组件、跨层级组件间的通信适合小型项目。创建 Event Bus// event-bus.jsimportVuefromvue;exportconstEventBusnewVue();发送方组件 (ComponentA.vue)template div !-- 1. 点击按钮通过全局总线发送数据 -- button click$bus.$emit(share-data, 我是A组件的数据)发送数据给B/button /div /template script import { EventBus } from ./event-bus.js; export default { created() { this.$bus EventBus; } } /script接收方组件 (ComponentB.vue)template div p接收到的消息{{ receivedMsg }}/p /div /template script import { EventBus } from ./event-bus.js; export default { data() { return { receivedMsg: } }, mounted() { // 2. 在挂载时监听全局总线的事件 this.$bus EventBus; this.$bus.$on(share-data, (data) { this.receivedMsg data; console.log(B组件收到了:, data); }); }, beforeDestroy() { // 3. 组件销毁前务必解绑事件防止内存泄漏 this.$bus.$off(share-data); } } /scriptEvent Bus 优缺点优点实现任意组件间通信简单灵活缺点事件名容易冲突难以追踪事件流向容易造成内存泄漏忘记解绑不适合大型项目6. 共同父组件中转传值范围兄弟组件之间子传父父再传子适用场景两个兄弟组件需要通信通过共同的父组件作为中转站数据流清晰可控。发送方兄弟组件 (BrotherA.vue)template div !-- 1. 触发事件把数据传给父组件 -- button click$emit(send-to-b, 来自兄弟A的消息)通知兄弟B/button /div /template共同父组件 (Parent.vue)template div !-- 2. 监听A的事件把数据存起来再通过props传给B -- BrotherA send-to-bhandleSend / BrotherB :msgmsgForB / /div /template script export default { data() { return { msgForB: } }, methods: { handleSend(data) { this.msgForB data; } } } /script接收方兄弟组件 (BrotherB.vue)template div !-- 3. 渲染父组件传过来的数据 -- p收到A的消息{{ msg }}/p /div /template script export default { props: [msg] } /script核心要点兄弟组件本身不能直接通信必须通过父组件数据流BrotherA → $emit → Parent → props → BrotherB适合组件层级较浅、逻辑简单的场景7. Provide / Inject依赖注入传值范围祖先组件传给任意深度的后代组件跨层级单向传递适用场景深层嵌套组件需要获取祖先组件的数据避免通过每一层 props 逐层传递prop 逐级透传。祖先组件 (Grandpa.vue)script export default { data() { return { theme: dark } }, // 1. 提供数据所有后代都能拿到 provide() { return { theme: this.theme } } } /script任意后代组件 (DeepChild.vue)template div !-- 2. 直接使用注入的数据不需要中间层传递 -- p当前主题{{ theme }}/p /div /template script export default { // 声明需要注入的数据 inject: [theme] } /script注意事项provide的数据不是响应式的除非传入响应式对象因此适合注入常量或一次性配置若需要响应式数据可以传一个Vue.observable对象或使用computed属性滥用provide/inject会造成数据来源不清晰应谨慎使用8. $attrs / $listeners跨代透传传值范围祖先组件传给深层后代中间组件只做透传不消费数据适用场景封装高阶组件时将父组件传入的未声明 props 和事件自动传递给下层组件避免手动一一传递。祖先组件 (App.vue)template div !-- 1. 传递属性和事件给中间组件 -- MiddleComp :titletitle click-btnhandleClick / /div /template script export default { data() { return { title: hello } }, methods: { handleClick() { console.log(按钮被点击); } } } /script中间组件 (MiddleComp.vue)template div !-- 2. 自己不消费直接把所有未声明的属性和事件透传给孙子组件 -- GrandChild v-bind$attrs v-on$listeners / /div /template script export default { // 3. 关闭默认绑定到根元素的行为让透传更干净 inheritAttrs: false } /script孙子组件 (GrandChild.vue)template div !-- 4. 正常接收祖先组件传过来的 props 和事件 -- p标题{{ title }}/p button click$emit(click-btn)触发祖先事件/button /div /template script export default { props: [title] } /script核心要点$attrs包含父作用域中不作为 prop 被识别的 attribute 绑定class 和 style 除外$listeners包含父作用域中的 v-on 事件监听器不含.native修饰符inheritAttrs: false可以阻止默认的根元素继承让透传更干净在 Vue3 中$listeners被合并到$attrs中事件也以onXxx形式存在9. $parent / $children传值范围父子组件之间直接访问实例破坏封装性极不推荐适用场景应急情况下快速访问父子组件实例但绝不应作为常规通信方式。父组件 (Parent.vue)template div Child / !-- 1. 父组件通过 $children 访问子组件注意是个数组且无序 -- button click$children[0].childMethod()调用子组件方法/button /div /template子组件 (Child.vue)script export default { data() { return { childMsg: 我是子组件的数据 } }, methods: { childMethod() { console.log(子组件方法被调用了); } }, mounted() { // 2. 子组件通过 $parent 直接读取/修改父组件的数据 console.log(this.$parent.childMsg); } } /script为什么不推荐强耦合子组件与父组件紧绑定难以复用顺序不可靠$children的顺序不保证与模板顺序一致难以维护直接修改父子组件的数据会让数据流混乱调试困难替代方案优先使用 props、事件、Vuex 等单向数据流方案四、vue3组件通信Vue3 在组件通信方面进行了全面升级引入了 Composition API 和多个编译器宏让通信方式更加简洁和灵活。1. Props / Emits最核心、最推荐传值范围Props父传子Emits子传父适用场景父子组件之间的直接通信使用script setup语法后声明更简洁是 Vue3 中最推荐的通信方式。子组件 (Child.vue)template div !-- 2. 渲染父组件传过来的数据 -- p接收到的消息{{ msg }}/p !-- 4. 点击按钮触发事件并把数据传回给父组件 -- button clickemit(change, 我是子组件传回的数据)通知父组件/button /div /template script setup // 1. 声明接收父组件的数据 defineProps([msg]) // 3. 声明要触发的事件 const emit defineEmits([change]) /script父组件 (Parent.vue)template div !-- 5. 把数据传给子组件并监听子组件的事件 -- Child :msgparentMsg changehandleChildEvent / /div /template script setup import { ref } from vue const parentMsg ref(我是父组件的数据) // 6. 接收子组件传过来的数据 const handleChildEvent (data) { console.log(data) // 输出: 我是子组件传回的数据 } /script核心要点defineProps和defineEmits是编译器宏无需导入即可使用子组件通过defineEmits声明事件返回的emit函数用于触发事件父组件使用事件名监听和 Vue2 的$emit用法一致数据流依然是单向的父 → 子props子 → 父emits2. v-model / defineModel双向绑定传值范围父子双向通信父传子 modelValue子传父 update:modelValue 事件适用场景表单元素或需要双向绑定的自定义组件Vue3.4 推荐使用defineModel宏代码更简洁。子组件 (Child.vue)template div !-- 2. 直接绑定 defineModel 返回的变量修改它会自动同步给父组件 -- input v-modelmodel placeholder请输入内容 / /div /template script setup // 1. 使用 Vue3.4 推荐的 defineModel 宏 const model defineModel() /script父组件 (Parent.vue)template div p父组件的值{{ text }}/p !-- 3. 使用 v-model实现父子数据双向同步 -- Child v-modeltext / /div /template script setup import { ref } from vue const text ref() /scriptv-model 进阶用法写法说明Child v-modeltext /默认绑定modelValue属性和update:modelValue事件Child v-model:titletitle /自定义参数绑定title属性和update:title事件多个 v-modelChild v-model:namename v-model:ageage /子组件中用defineModel(name)和defineModel(age)分别声明低版本兼容写法Vue3.4 之前script setup const props defineProps([modelValue]) const emit defineEmits([update:modelValue]) const model computed({ get: () props.modelValue, set: (value) emit(update:modelValue, value) }) /script3. Ref DefineExpose父组件直接访问子组件传值范围父组件单向访问子组件直接获取子组件暴露的实例适用场景父组件需要直接调用子组件的方法或访问其数据如表单校验、触发动画、获取子组件状态等。子组件 (Child.vue)template input v-modelinputValue placeholder请输入内容 / /template script setup import { ref } from vue const inputValue ref() // 2. 显式暴露给父组件调用的方法 const validate () { if (!inputValue.value) { alert(内容不能为空) return false } return true } // 3. 必须通过 defineExpose 暴露父组件才能访问 defineExpose({ validate }) /script父组件 (Parent.vue)template div !-- 1. 给子组件打上 ref 标记 -- Child refmyFormRef / button clickhandleSubmit提交/button /div /template script setup import { ref } from vue const myFormRef ref(null) const handleSubmit () { // 4. 通过 ref.value 直接调用子组件暴露的方法 const isValid myFormRef.value.validate() if (isValid) console.log(验证通过) } /script使用注意事项在script setup中组件默认是“封闭”的必须通过defineExpose显式暴露属性或方法父组件的ref变量名需与模板中的ref属性值一致访问子组件实例需在onMounted或之后的生命周期中确保组件已挂载过度使用ref访问子组件会破坏封装性应优先使用 props 和 emits4. $attrs属性透传传值范围祖先组件传给深层后代中间组件只做透传不消费数据适用场景封装高阶组件或基础 UI 组件时将父组件传入的未声明 props 和事件自动传递给下层组件避免手动一一传递。祖先组件 (App.vue)template div !-- 1. 传递属性和事件给中间组件 -- MiddleComp :titletitle click-btnhandleClick / /div /template script setup import { ref } from vue const title ref(hello) const handleClick () { console.log(按钮被点击) } /script中间组件 (MiddleComp.vue)template div !-- 2. 自己不消费直接把所有未声明的属性和事件透传给孙子组件 -- GrandChild v-bind$attrs / /div /template script setup // 3. 关闭默认绑定到根元素的行为让透传更干净 defineOptions({ inheritAttrs: false }) /script孙子组件 (GrandChild.vue)template div !-- 4. 正常接收祖先组件传过来的 props 和事件 -- p标题{{ title }}/p button click$emit(click-btn)触发祖先事件/button /div /template script setup defineProps([title]) /script核心要点在 Vue3 中$listeners已被合并到$attrs中事件监听器以onXxx的形式存在中间的透传组件通过v-bind$attrs即可将所有未声明的属性和事件一次传递给下层组件使用defineOptions({ inheritAttrs: false })可以阻止默认的根元素继承避免属性污染孙子组件中通过defineProps声明需要的属性通过$emit触发事件即可5. Provide / Inject依赖注入传值范围祖先组件传给任意深度的后代组件跨层级单向传递适用场景深层嵌套组件需要获取祖先组件的数据避免通过每一层 props 逐层传递适合全局配置如主题、语言、权限等和跨层级共享状态。祖先组件 (Grandpa.vue)template div DeepChild / /div /template script setup import { provide, ref } from vue const theme ref(dark) // 1. 提供数据所有后代都能拿到 provide(theme, theme) /script任意后代组件 (DeepChild.vue)template div !-- 2. 直接使用注入的数据不需要中间层传递 -- p当前主题{{ theme }}/p /div /template script setup import { inject } from vue // 声明需要注入的数据 const theme inject(theme) /script核心要点在 Vue3 的script setup中使用provide和inject函数需要从vue中导入provide的第一个参数是注入的 key字符串或 Symbol第二个参数是值inject的第一个参数是 key第二个参数是可选默认值与 Vue2 不同Vue3 的provide数据默认是响应式的如果传入的是 ref 或 reactive 对象后代组件可以直接响应变化适合替代 Vuex/Pinia 的部分功能用于跨层级共享一些简单的状态注意事项要修改注入的数据推荐在祖先组件中提供修改方法而不是在后代组件中直接修改保持单向数据流使用 Symbol 作为 key 可以避免命名冲突对于复杂的全局状态管理仍推荐使用 Pinia6. Mitt 事件总线传值范围任意组件间通信常用于兄弟组件、无直接关系的组件适用场景Vue3 中移除了$on、$off、$once等实例方法无法使用 Vue2 的 Event Bus。推荐使用第三方库mitt实现轻量级全局事件总线适合小型项目或需要跨组件通信的场景。安装 mittnpminstallmitt创建 mitt 实例// utils/eventBus.jsimportmittfrommittconstemittermitt()exportdefaultemitter发送方组件 (ComponentA.vue)template div !-- 1. 点击按钮通过全局总线发送数据 -- button clickemitter.emit(share-data, 我是A组件的数据)发送数据给B/button /div /template script setup import emitter from /utils/eventBus // 引入配置好的 mitt 实例 /script接收方组件 (ComponentB.vue)template div p接收到的消息{{ receivedMsg }}/p /div /template script setup import { ref, onMounted, onUnmounted } from vue import emitter from /utils/eventBus const receivedMsg ref() const handleData (data) { receivedMsg.value data console.log(B组件收到了:, data) } // 2. 在挂载时监听全局总线的事件 onMounted(() emitter.on(share-data, handleData)) // 3. 组件卸载时务必解绑事件防止内存泄漏 onUnmounted(() emitter.off(share-data, handleData)) /scriptmitt 核心 API方法说明emitter.emit(event, data)触发事件传递数据emitter.on(event, callback)监听事件当事件触发时执行回调emitter.off(event, callback)解绑指定事件和回调emitter.all.clear()清除所有事件监听器使用注意事项事件名建议使用常量或字符串枚举避免硬编码分散必须成对使用on和off在onUnmounted中解绑防止内存泄漏mitt 适合小型应用大型项目推荐使用 Pinia 或 Vuex 作为状态管理7. 共同父组件中转传值范围兄弟组件之间子传父父再传子适用场景两个兄弟组件需要通信通过共同的父组件作为中转站数据流清晰可控适合组件层级较浅、逻辑简单的场景。发送方兄弟组件 (BrotherA.vue)template div !-- 1. 触发事件把数据传给父组件 -- button click$emit(send-to-b, 来自兄弟A的消息)通知兄弟B/button /div /template共同父组件 (Parent.vue)template div !-- 2. 监听A的事件把数据存起来再通过props传给B -- BrotherA send-to-bhandleSend / BrotherB :msgmsgForB / /div /template script setup import { ref } from vue const msgForB ref() const handleSend (data) { msgForB.value data } /script接收方兄弟组件 (BrotherB.vue)template div !-- 3. 渲染父组件传过来的数据 -- p收到A的消息{{ msg }}/p /div /template script setup defineProps([msg]) /script核心要点兄弟组件本身不能直接通信必须通过父组件作为中介数据流BrotherA → emits → Parent → props → BrotherB使用script setup语法后通过$emit触发事件通过defineProps接收数据适合简单的兄弟组件通信如果多个兄弟组件或复杂状态建议使用状态管理Pinia或事件总线
返回列表