Vue 组件的通信方式有哪些?

通信方式可以根据组件之间的关系分为以下几大类:

一、父子组件通信(最常用)

这是最常见的数据流,包括父传子和子传父。

1. 父传子:props

最基础、最推荐的方式。父组件通过属性(Attribute)传递数据,子组件通过 defineProps 接收。

子组件 (Child.vue)

复制代码
<template>
  <div>
    <h2>子组件</h2>
    <p>收到来自父组件的消息:{{ message }}</p>
    <p>数量:{{ count }}</p>
  </div>
</template>

<script setup>
// 使用 defineProps 来声明 props
const props = defineProps({
  message: String,
  count: {
    type: Number,
    default: 0
  }
})
</script>

父组件 (Parent.vue)

复制代码
<template>
  <div>
    <h1>父组件</h1>
    <!-- 像传递 HTML 属性一样传递 props -->
    <Child :message="parentMessage" :count="num" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const parentMessage = ref('Hello from Parent!')
const num = ref(42)
</script>

2. 子传父:$emit / 自定义事件

子组件通过触发(emit)事件来向父组件传递数据。父组件监听这个事件并执行回调函数。

子组件 (Child.vue)

复制代码
<template>
  <button @click="sendMessageToParent">点击向父组件发送消息</button>
</template>

<script setup>
// 使用 defineEmits 来声明要触发的事件
const emit = defineEmits(['messageToParent'])

const sendMessageToParent = () => {
  // 触发事件,并传递数据
  emit('messageToParent', 'Hello Parent! from Child')
}
</script>

父组件 (Parent.vue)

复制代码
<template>
  <div>
    <p>来自子组件的消息:{{ childMessage }}</p>
    <!-- 监听子组件触发的事件 -->
    <Child @message-to-parent="handleMessageFromChild" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const childMessage = ref('')

const handleMessageFromChild = (msg) => {
  childMessage.value = msg
}
</script>

3. 父组件直接访问子组件:ref 和 defineExpose

父组件可以通过 ref 获取子组件的实例,并调用其方法或访问其数据。

子组件 (Child.vue)

复制代码
<template>
  <div>{{ childData }}</div>
</template>

<script setup>
import { ref } from 'vue'

const childData = ref('子组件的数据')
const childMethod = () => {
  console.log('子组件的方法被调用了')
}

// 使用 defineExpose 暴露数据或方法,父组件才能访问到
defineExpose({
  childData,
  childMethod
})
</script>

父组件 (Parent.vue)

复制代码
<template>
  <div>
    <button @click="callChildMethod">调用子组件方法</button>
    <Child ref="childRef" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

// 声明一个与子组件同名的 ref
const childRef = ref(null)

const callChildMethod = () => {
  // 通过 .value 访问子组件实例,并调用其暴露出的方法或数据
  childRef.value?.childMethod()
  console.log(childRef.value?.childData)
}
</script>

二、兄弟组件或任意组件通信

当组件没有直接的父子关系时,上面的方法就很麻烦。这时需要"全局"或"跨级"的通信方案。

4. provide / inject(依赖注入)

适合深层嵌套的组件通信。祖先组件提供(provide)数据,任意层级的后代组件都可以注入(inject)使用。

祖先组件 (Ancestor.vue)

复制代码
<template>
  <div>
    <Parent />
  </div>
</template>

<script setup>
import { ref, provide } from 'vue'
import Parent from './Parent.vue'

const theme = ref('dark')
const user = ref({ name: 'Alice' })

// 提供数据,可以提供一个响应式的 ref 使其保持响应性
provide('themeColor', theme)
provide('userInfo', user)
</script>

后代组件 (Descendant.vue)

复制代码
<template>
  <div :class="theme">主题颜色是:{{ theme }}</div>
</template>

<script setup>
import { inject } from 'vue'

// 注入数据,第二个参数是默认值(可选)
const theme = inject('themeColor', 'light') // 如果找不到 'themeColor',就使用 'light'
const user = inject('userInfo')
</script>

5. 全局事件总线(Event Bus)

Vue 3 中官方删除了 on, off 等实例方法,但我们可以使用第三方库(如 mitt 或 tiny-emitter)来实现一个轻量级的事件总线。

  1. 创建事件总线 (eventBus.js)

    // 使用 mitt 库
    import mitt from 'mitt'
    const emitter = mitt()
    export default emitter

  2. 组件 A (发送事件)

  3. 组件 B (接收事件)

6. 状态管理库(Pinia / Vuex)

对于复杂的大型应用,数据流可能非常混乱。使用状态管理库(Vue 3 官方推荐 Pinia)将共享状态抽离出来集中管理,是更专业、可预测的选择。

使用 Pinia 的简单示例:

  1. 创建 Store (stores/counter.js)

    import { defineStore } from 'pinia'

    复制代码
     export const useCounterStore = defineStore('counter', {
       state: () => ({ count: 0 }),
       actions: {
         increment() {
           this.count++
         }
       }
     })
  2. 在任何组件中使用

    复制代码
     <script setup>
     import { useCounterStore } from '@/stores/counter'
     const store = useCounterStore()
     </script>

三、其他方式

• Vuex/Pinia 的替代方案:对于简单场景,也可以使用 reactive 创建一个全局响应式对象,然后到处导入使用(但缺乏像 Pinia 那样的调试工具和规范,不适合大型项目)。

• 使用 localStorage/sessionStorage:用于页面刷新后数据依然存在的场景,但非响应式,需要自己监听 storage 事件。

总结与选择建议

通信方式 关系 场景

props / $emit 父子组件 最常用,简单的数据传递

ref / defineExpose 父子组件 父组件需要直接调用子组件方法时

provide / inject 祖先-后代 深层嵌套组件,如主题、用户信息等

全局事件总线 任意组件 跨组件层级简单事件通知,中小型项目

Pinia (状态管理库) 任意组件 中大型项目,复杂的共享状态管理

简单口诀:

• 父子通信:props 下去,emit 上来。

• 父调子:用 ref。

• 祖孙通信:用 provide/inject。

• 任意通信:简单用事件总线,复杂用 Pinia。

相关推荐
砚凝霜13 分钟前
软考网络工程师|案例分析:Eth‑Trunk 链路聚合、iStack 堆叠、CSS 集群核心考点总结
前端·css·网络
二级小助手23 分钟前
二级Web前端选择题高频真题20道与考场避坑笔记
javascript·css3·html5·web前端·计算机二级·二级web·web真题
珐恩AI-人工智能34 分钟前
大模型意图召回偏差分析:GEO如何解决“有收录却不触发问答曝光”的难题
大数据·前端·人工智能·html·流量运营·geo优化
程序员老赵1 小时前
Docker 部署禅道 ZenTao:轻松搭建研发项目管理平台
前端·后端·github
前端小卡拉1 小时前
用了大半年 AI 编程工具,我才搞懂 Skill 到底是什么
前端·javascript
hunterandroid1 小时前
[鸿蒙从零到一] HarmonyOS 单元测试与 UI 自动化测试实战:从代码质量到用户体验的全链路保障
前端
郭邯1 小时前
用 Intl.DateTimeFormat 手写一个时区转换器,顺便聊聊我和 AI 结对编程的日常
前端·javascript
阿奇__1 小时前
微前端、iframe与Auth Code技术方案对比
前端
FungLeo1 小时前
React 管理后台实战 · 前端请求层怎么写?401 静默刷新、token 并发竞争、强制改密拦截一次说清
前端·react.js·前端框架
mashirro2 小时前
记一次基于ruoyi-vue新增小程序服务模块,小程序登陆相关
vue.js·小程序·mybatis