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 (发送事件)

    <script setup> import emitter from './eventBus.js'

    const sendMessage = () => {
    emitter.emit('global-event', { data: '任意数据' })
    }
    </script>

  3. 组件 B (接收事件)

    <script setup> import { onMounted, onUnmounted } from 'vue' import emitter from './eventBus.js'

    const handleEvent = (data) => {
    console.log('收到消息:', data)
    }

    // 组件挂载时监听事件
    onMounted(() => {
    emitter.on('global-event', handleEvent)
    })

    // 组件卸载时取消监听,防止内存泄漏
    onUnmounted(() => {
    emitter.off('global-event', handleEvent)
    })
    </script>

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. 在任何组件中使用

    <template>
    {{ store.count }}
    <button @click="store.increment()">+1</button> </template>
    复制代码
     <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。

相关推荐
k09331 小时前
vue中view-design的校验及各种坑
前端·vue.js·view design
乘风gg2 小时前
企业级 Prompt 工程实战指南(下):构建可复用 Prompt 架构平台
前端·面试·架构
Kyl2n2 小时前
【密码口令保存小工具】
javascript·css·css3
宇擎智脑科技2 小时前
AntV G6、X6 与 React Flow 深度对比:核心差异与大模型时代的应用场景分析
前端·人工智能·react.js·前端框架
山核桃&17°2 小时前
基于 Vue + Node.js 批处理bat脚本实现多环境一键部署
运维·前端·自动化
AC赳赳老秦2 小时前
云原生AI趋势:DeepSeek与云3.0架构协同,提升AI部署性能与可移植性
大数据·前端·人工智能·算法·云原生·架构·deepseek
kilito_012 小时前
js实现 移动动画 封装
javascript
程序哥聊面试2 小时前
React + TS 初始化新项目报错解决方法
前端·react.js·npm
codeGoogle2 小时前
2026 年 IM 怎么选?聊聊 4 家主流即时通讯方案的差异
android·前端·后端