Vue3 组件通信全解:从父子到跨层级,项目实战全覆盖
2026/6/9 9:18:17 网站建设 项目流程

文章目录

    • 一、前置基础知识
      • 1. 什么是组件通信
      • 2. 父子组件判定规则(固定不变)
      • 3. 原生事件 vs 自定义事件
    • 二、父子组件通信(使用频率最高)
      • 2.1 父组件向子组件传值:defineProps
        • 适用场景
        • 完整代码示例
        • 补充要点
      • 2.2 子组件向父组件传值:defineEmits 自定义事件
        • 两种实现思路
        • 核心流程
        • 完整代码示例
      • 2.3 父组件调用子组件方法/获取子组件数据:ref + defineExpose
        • 适用场景
        • 核心规则
        • 完整代码示例
      • 2.4 父子组件双向绑定:v-model
        • 适用场景
        • 完整代码示例
    • 三、跨层级组件通信(祖孙/隔多层组件)
      • 3.1 $attrs 透传(简单跨层级)
        • 原理
        • 特点
        • 代码示例(爷 → 父 → 孙)
      • 3.2 provide / inject(专属跨层级)
    • 四、兄弟组件通信
      • 方案:借助父组件中转
        • 执行流程
        • 适用场景
    • 五、全局任意组件通信(无组件关系限制)
      • 5.1 Pinia(项目首选,全局状态管理)
        • 1. 安装
        • 2. 全局挂载(main.js)
        • 3. 定义全局仓库 store
        • 4. 任意组件使用
      • 5.2 Mitt 事件总线(轻量发布订阅)
        • 适用场景
        • 1. 安装
        • 2. 统一创建总线实例
        • 3. 四大核心 API
        • 4. 实战使用
        • 数据流向
    • 六、场景选型总结(项目直接对照使用)
      • 一句话口诀
    • 七、注意事项

在 Vue3 开发中,组件拆分是常态,组件通信更是日常开发的基础技能。面对父子、兄弟、跨层级组件传值时总是容易混淆用法、踩坑不断。

一、前置基础知识

1. 什么是组件通信

组件之间互相传递数据、调用方法,统称为组件通信。Vue 项目几乎所有业务都离不开组件传值。

2. 父子组件判定规则(固定不变)

  • 父组件:通过import引入组件、在模板中使用组件标签的一方
  • 子组件:被引入、被当作标签使用的一方
  • 注意:组件不能互相import,会造成循环引用,直接报错

3. 原生事件 vs 自定义事件

这是新手高频混淆点,重点区分:

  1. 原生事件:如clickmouseenterinput等浏览器内置事件
    • $event:原生事件对象,包含鼠标坐标、触发元素、按键信息等原生属性
  2. 自定义事件:开发者自定义事件名,无固定规则
    • $event:等同于$emit传递的自定义数据,可以是任意类型

二、父子组件通信(使用频率最高)

父子组件是项目中最常见的组件关系,分为父传子、子传父、父调用子方法、双向绑定四类场景。

2.1 父组件向子组件传值:defineProps

适用场景

父组件把数据传递给子组件,单向数据流。

完整代码示例

父组件

<template> <!-- 通过属性绑定向子组件传值 --> <Child :msg="父组件消息" :list="arr" /> </template> <script setup> import Child from './Child.vue' const arr = [1, 2, 3] </script>

子组件(接收数据)

<script setup> // 方式1:基础类型校验(推荐,规范项目必用) const props = defineProps({ msg: String, list: Array }) // 方式2:简易写法(仅快速接收,不做类型校验) // const props = defineProps(['msg', 'list']) // 模板/逻辑中直接使用 props.xxx console.log(props.msg) </script> <template> <div>{{ msg }}</div> <div>{{ list }}</div> </template>
补充要点
  1. 数据流为单向:子组件不要直接修改props数据,会破坏数据流向、引发 Bug
  2. 支持类型校验、默认值、必填校验,适合团队协作规范约束

2.2 子组件向父组件传值:defineEmits 自定义事件

两种实现思路
  1. 主流方案:子组件通过defineEmits派发自定义事件传值(官方推荐)
  2. 间接方案:父组件通过props传递回调函数,子组件执行函数传参(原理一致)
核心流程

子组件触发动作 →emit派发事件+携带数据 → 父组件监听同名事件 → 接收数据并执行逻辑

完整代码示例

子组件(发送数据)

<template> <button @click="sendData">向父组件传值</button> </template> <script setup> // 1. 定义可派发的自定义事件 const emit = defineEmits(['sendToParent']) // 2. 触发事件,传递自定义数据 const sendData = () => { emit('sendToParent', '我是来自子组件的数据') } </script>

父组件(接收数据)

<template> <!-- 监听子组件派发的自定义事件 --> <Child @sendToParent="handleGetData" /> </template> <script setup> // val 自动接收子组件传递过来的数据 const handleGetData = (val) => { console.log('父组件收到:', val) } </script>

2.3 父组件调用子组件方法/获取子组件数据:ref + defineExpose

适用场景

父组件主动操作子组件,调用子组件内部方法、读取子组件数据。

核心规则

<script setup>语法下,组件内部属性和方法默认对外关闭,必须通过defineExpose主动暴露,父组件才能访问。

完整代码示例

父组件

<template> <Child ref="childRef" /> <button @click="callChildFn">调用子组件方法</button> </template> <script setup> import { ref } from 'vue' import Child from './Child.vue' // 定义 ref 关联子组件实例 const childRef = ref() const callChildFn = () => { // 通过 ref.value 访问子组件暴露的方法/数据 childRef.value.childSay() console.log(childRef.value.childNum) } </script>

子组件

<script setup> const childNum = 666 const childSay = () => { alert('子组件方法被调用了') } // 主动暴露方法/数据给父组件 defineExpose({ childSay, childNum }) </script>

2.4 父子组件双向绑定:v-model

适用场景

父子组件数据需要双向同步,一方修改,另一方自动更新。

Vue3 中组件v-model本质是props + 自定义事件的语法糖。

完整代码示例

父组件

<template> <!-- 双向绑定数据 --> <Child v-model="inputValue" /> <p>父组件值:{{ inputValue }}</p> </template> <script setup> import { ref } from 'vue' import Child from './Child.vue' const inputValue = ref('') </script>

子组件

<template> <!-- 绑定 props 数据,触发 update 事件更新数据 --> <input :value="modelValue" @input="handleInput" /> </template> <script setup> // 接收默认 props:modelValue const props = defineProps(['modelValue']) // 声明更新事件:update:modelValue const emit = defineEmits(['update:modelValue']) const handleInput = (e) => { // 触发事件,同步数据到父组件 emit('update:modelValue', e.target.value) } </script>

三、跨层级组件通信(祖孙/隔多层组件)

当组件层级较多(爷-父-孙、多层嵌套),一层层传props/emit代码冗余,推荐两种方案:$attrs透传、provide/inject

3.1 $attrs 透传(简单跨层级)

原理

上层组件传递的属性、自定义事件,若中间组件不使用defineProps接收,会自动存入$attrs;中间组件通过v-bind="$attrs"一键透传给下层组件。

特点
  • 中间组件零代码改造,只做中转
  • 适合简单数据、事件透传,不适合复杂全局状态
代码示例(爷 → 父 → 孙)

爷组件(最上层)

<template> <!-- 向子组件传递属性和事件 --> <Father :info="爷组件数据" @toGrandpa="getMsg" /> </template> <script setup> const getMsg = (val) => { console.log('爷组件收到孙组件数据:', val) } </script>

中间父组件(中转层,核心)

<template> <!-- 整包透传 $attrs 到孙组件 --> <Son v-bind="$attrs" /> </template> <script setup> // 不写 defineProps,数据和事件自动进入 $attrs </script>

孙组件(最下层)

<script setup> // 接收上层透传的数据 const props = defineProps(['info']) // 触发上层自定义事件 const emit = defineEmits(['toGrandpa']) const sendToGrandpa = () => { emit('toGrandpa', '孙组件的数据') } </script>

3.2 provide / inject(专属跨层级)

Vue 官方专为多层嵌套跨层级传值设计的 API,下文结合实战简述,适合静态数据、配置类数据传递。

补充:provide/inject支持祖先组件向任意后代组件传值,不受层级限制,后代组件可直接注入使用。


四、兄弟组件通信

方案:借助父组件中转

兄弟组件没有直接关联,通用思路:A兄弟 → 父组件 → B兄弟

执行流程
  1. 兄弟A 通过defineEmits触发自定义事件,把数据传给父组件
  2. 父组件接收数据,通过defineProps把数据传给兄弟B
适用场景

简单兄弟传值,层级少、逻辑简单的场景。复杂场景建议使用mittPinia


五、全局任意组件通信(无组件关系限制)

适用于任意页面、任意组件、任意层级的数据共享,是中大型项目的核心方案,分为两大主流工具:PiniaMitt 事件总线

5.1 Pinia(项目首选,全局状态管理)

Vue 官方推荐状态管理库,替代 Vuex,轻量化、语法简洁,90% 的项目全局数据都用它
适合场景:用户信息、Token、购物车、全局配置等需要全局共享、频繁修改的数据。

1. 安装
npminstallpinia
2. 全局挂载(main.js)
import{createApp}from'vue'importAppfrom'./App.vue'import{createPinia}from'pinia'constapp=createApp(App)app.use(createPinia())app.mount('#app')
3. 定义全局仓库 store

新建src/stores/test.js

import{defineStore}from'pinia'import{ref}from'vue'// 定义仓库,唯一ID:testexportconstuseTestStore=defineStore('test',()=>{// 全局响应式数据constnum=ref(100)constchangeNum=(val)=>{num.value=val}// 对外暴露数据和方法return{num,changeNum}})
4. 任意组件使用
<script setup> import { storeToRefs } from 'pinia' import { useTestStore } from '@/stores/test' // 获取仓库实例 const testStore = useTestStore() // 解构数据,保持响应式 const { num } = storeToRefs(testStore) // 直接调用仓库方法 testStore.changeNum(200) </script>

5.2 Mitt 事件总线(轻量发布订阅)

Vue3 移除了原生$on/$off事件总线,官方推荐使用mitt,体积仅 200B、零依赖。

适用场景

兄弟组件、多层跨组件临时通信、全局消息通知;不适合管理大型全局状态(优先用 Pinia)。

1. 安装
npmi mitt
2. 统一创建总线实例

新建src/utils/eventBus.js

importmittfrom'mitt'// 创建全局事件总线exportdefaultmitt()
3. 四大核心 API
  • bus.on(事件名, 回调):监听事件(接收数据)
  • bus.emit(事件名, 数据):触发事件(发送数据)
  • bus.off(事件名, 回调):取消监听(必写,防止内存泄漏
  • bus.all.clear():清空所有事件
4. 实战使用

发送数据的组件

<script setup> import bus from '@/utils/eventBus' const sendMsg = () => { // 触发事件并传值 bus.emit('globalMsg', '来自事件总线的消息') } </script>

接收数据的组件

<script setup> import { onMounted, onBeforeUnmount } from 'vue' import bus from '@/utils/eventBus' // 抽离回调函数,方便后续取消监听 const handleMsg = (val) => { console.log('接收数据:', val) } // 组件挂载时监听事件 onMounted(() => { bus.on('globalMsg', handleMsg) }) // 组件销毁时,取消监听(关键!避免内存泄漏) onBeforeUnmount(() => { bus.off('globalMsg', handleMsg) }) </script>
数据流向

发送组件emit→ 全局总线中转 → 监听组件on接收回调


六、场景选型总结(项目直接对照使用)

通信场景推荐方案
父组件 → 子组件 传值defineProps
子组件 → 父组件 传值defineEmits自定义事件
父组件调用子组件方法ref + defineExpose
父子数据双向同步v-model
多层跨层级(祖孙)传值$attrs/provide/inject
兄弟组件简单通信父组件中转
全局组件共享状态Pinia(首选)
临时跨组件消息通知Mitt 事件总线

一句话口诀

关系近用 props/emit,层级多用透传,全局共享直接上 Pinia!


七、注意事项

  1. 禁止直接修改props数据,遵循单向数据流;
  2. 使用mitt必须在组件销毁时off取消监听,防止内存泄漏;
  3. <script setup>中方法/属性默认不对外暴露,父组件调用子组件必须配合defineExpose
  4. 大型项目全局状态统一使用 Pinia,不要混用多种通信方案,降低维护成本。

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

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

立即咨询