一、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,完美支持 TypeScript,API 更简洁。 |
| 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 :msg="parentMsg" @change="handleChildEvent" /> </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 :value="value" @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-model="text" /> </div> </template> <script> export default { data() { return { text: '' } } } </script>v-model 原理:
| 写法 | 等价写法 | 说明 |
|---|---|---|
<Child v-model="text" /> | <Child :value="text" @input="text = $event" /> | 默认绑定 value 属性和 input 事件 |
v-model.trim="text" | :value="text" @input="text = $event.trim()" | 带修饰符的 v-model |
自定义 v-model:在子组件中通过model选项可以修改默认的 prop 和事件名。
exportdefault{model:{prop:'checked',// 修改默认的 value 属性名event:'change'// 修改默认的 input 事件名},props:['checked']}3. .sync 修饰符(Vue2 特有)
传值范围:父子双向通信(父传子 属性,子传父 update:属性 事件)
适用场景:需要双向绑定的多个 props,Vue3 中已移除,改用多个 v-model 实现。
子组件 (Child.vue):
<template> <div v-if="visible"> <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 @click="isShow = true">打开弹窗</button> <!-- 3. 使用 .sync,子组件修改时会自动同步到 isShow --> <Child :visible.sync="isShow" /> </div> </template> <script> export default { data() { return { isShow: false } } } </script>.sync 修饰符原理:
| 语法糖写法 | 等价写法 | 说明 |
|---|---|---|
:visible.sync="isShow" | :visible="isShow" @update:visible="isShow = $event" | 单个属性双向同步 |
| 支持多个属性 | :width.sync="w" :height.sync="h" | 多个属性分别同步 |
4. $refs(父组件直接访问子组件)
传值范围:父组件单向访问子组件(直接获取子组件实例)
适用场景:父组件需要直接调用子组件的方法或访问其数据,如表单校验。
子组件 (Child.vue):
<template> <input v-model="inputValue" 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 ref="myForm" /> <button @click="handleSubmit">提交</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.jsimportVuefrom'vue';exportconstEventBus=newVue();发送方组件 (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'); } } </script>Event 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-b="handleSend" /> <BrotherB :msg="msgForB" /> </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 :title="title" @click-btn="handleClick" /> </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 @click="emit('change', '我是子组件传回的数据')">通知父组件</button> </div> </template> <script setup> // 1. 声明接收父组件的数据 defineProps(['msg']) // 3. 声明要触发的事件 const emit = defineEmits(['change']) </script>父组件 (Parent.vue):
<template> <div> <!-- 5. 把数据传给子组件,并监听子组件的事件 --> <Child :msg="parentMsg" @change="handleChildEvent" /> </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),子 → 父(emits)
2. v-model / defineModel(双向绑定)
传值范围:父子双向通信(父传子 modelValue,子传父 update:modelValue 事件)
适用场景:表单元素或需要双向绑定的自定义组件,Vue3.4+ 推荐使用defineModel宏,代码更简洁。
子组件 (Child.vue):
<template> <div> <!-- 2. 直接绑定 defineModel 返回的变量,修改它会自动同步给父组件 --> <input v-model="model" 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-model="text" /> </div> </template> <script setup> import { ref } from 'vue' const text = ref('') </script>v-model 进阶用法:
| 写法 | 说明 |
|---|---|
<Child v-model="text" /> | 默认绑定modelValue属性和update:modelValue事件 |
<Child v-model:title="title" /> | 自定义参数,绑定title属性和update:title事件 |
| 多个 v-model | <Child v-model:name="name" v-model:age="age" />,子组件中用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) }) </script>3. Ref + DefineExpose(父组件直接访问子组件)
传值范围:父组件单向访问子组件(直接获取子组件暴露的实例)
适用场景:父组件需要直接调用子组件的方法或访问其数据,如表单校验、触发动画、获取子组件状态等。
子组件 (Child.vue):
<template> <input v-model="inputValue" 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 ref="myFormRef" /> <button @click="handleSubmit">提交</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 和 emits
4. $attrs(属性透传)
传值范围:祖先组件传给深层后代(中间组件只做透传,不消费数据)
适用场景:封装高阶组件或基础 UI 组件时,将父组件传入的未声明 props 和事件自动传递给下层组件,避免手动一一传递。
祖先组件 (App.vue):
<template> <div> <!-- 1. 传递属性和事件给中间组件 --> <MiddleComp :title="title" @click-btn="handleClick" /> </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 可以避免命名冲突
- 对于复杂的全局状态管理,仍推荐使用 Pinia
6. Mitt 事件总线
传值范围:任意组件间通信(常用于兄弟组件、无直接关系的组件)
适用场景:Vue3 中移除了$on、$off、$once等实例方法,无法使用 Vue2 的 Event Bus。推荐使用第三方库mitt实现轻量级全局事件总线,适合小型项目或需要跨组件通信的场景。
安装 mitt:
npminstallmitt创建 mitt 实例:
// utils/eventBus.jsimportmittfrom'mitt'constemitter=mitt()exportdefaultemitter发送方组件 (ComponentA.vue):
<template> <div> <!-- 1. 点击按钮,通过全局总线发送数据 --> <button @click="emitter.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)) </script>mitt 核心 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-b="handleSend" /> <BrotherB :msg="msgForB" /> </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)或事件总线