Vue3 组件通信全解析

组件通信是 Vue 开发中绕不开的核心知识点,尤其是 Vue3 组合式 API 普及后,通信方式相比 Vue2 有了不少变化和优化。本文将抛开 TypeScript,用最通俗易懂的方式,带你梳理 Vue3 中所有常用的组件通信方式,从基础的父子通信到复杂的跨层级通信,每一种都配实战示例,新手也能轻松上手。

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

父子组件通信是日常开发中使用频率最高的场景,Vue3 为这种场景提供了清晰且高效的解决方案。

1. 父传子:Props

Props 是父组件向子组件传递数据的官方标准方式,子组件通过定义 props 接收父组件传递的值。

父组件(Parent.vue)

js 复制代码
<template>
  <div class="parent">
    <h3>我是父组件</h3>
    <!-- 向子组件传递数据 -->
    <Child 
      :msg="parentMsg" 
      :user-info="userInfo"
      :list="fruitList"
    />
  </div>
</template>

<script setup>
// 引入子组件
import Child from './Child.vue'
import { ref, reactive } from 'vue'

// 定义要传递给子组件的数据
const parentMsg = ref('来自父组件的问候')
const userInfo = reactive({
  name: '张三',
  age: 25
})
const fruitList = ref(['苹果', '香蕉', '橙子'])
</script>

子组件(Child.vue)

js 复制代码
<template>
  <div class="child">
    <h4>我是子组件</h4>
    <p>父组件传递的字符串:{{ msg }}</p>
    <p>父组件传递的对象:{{ userInfo.name }} - {{ userInfo.age }}岁</p>
    <p>父组件传递的数组:{{ list.join('、') }}</p>
  </div>
</template>

<script setup>
// 定义props接收父组件数据
const props = defineProps({
  // 字符串类型
  msg: {
    type: String,
    default: '默认值'
  },
  // 对象类型
  userInfo: {
    type: Object,
    default: () => ({}) // 对象/数组默认值必须用函数返回
  },
  // 数组类型
  list: {
    type: Array,
    default: () => []
  }
})

// 在脚本中使用props(组合式API中可直接用props.xxx)
console.log(props.msg)
</script>

2. 子传父:自定义事件(Emits)

子组件通过触发自定义事件,将数据传递给父组件,父组件通过监听事件接收数据。

子组件(Child.vue)

js 复制代码
<template>
  <div class="child">
    <h4>我是子组件</h4>
    <button @click="sendToParent">向父组件传递数据</button>
  </div>
</template>

<script setup>
// 声明要触发的自定义事件(可选,但推荐)
const emit = defineEmits(['childMsg', 'updateInfo'])

const sendToParent = () => {
  // 触发事件并传递数据(第一个参数是事件名,后续是要传递的数据)
  emit('childMsg', '来自子组件的消息')
  emit('updateInfo', {
    name: '李四',
    age: 30
  })
}
</script>

父组件(Parent.vue)

js 复制代码
<template>
  <div class="parent">
    <h3>我是父组件</h3>
    <!-- 监听子组件的自定义事件 -->
    <Child 
      @childMsg="handleChildMsg"
      @updateInfo="handleUpdateInfo"
    />
    <p>子组件传递的消息:{{ childMsg }}</p>
    <p>子组件更新的信息:{{ newUserInfo.name }} - {{ newUserInfo.age }}岁</p>
  </div>
</template>

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

const childMsg = ref('')
const newUserInfo = reactive({
  name: '',
  age: 0
})

// 处理子组件的消息
const handleChildMsg = (msg) => {
  childMsg.value = msg
}

// 处理子组件的信息更新
const handleUpdateInfo = (info) => {
  newUserInfo.name = info.name
  newUserInfo.age = info.age
}
</script>

二、跨层级组件通信

当组件嵌套层级较深(比如爷孙组件、跨多级组件),使用 props + emits 会非常繁琐,这时需要更高效的跨层级通信方案。

1. provide /inject(依赖注入)

provide 用于父组件(或祖先组件)提供数据,inject 用于子孙组件注入数据,支持任意层级的组件通信。

祖先组件(GrandParent.vue)

js 复制代码
<template>
  <div class="grand-parent">
    <h3>我是祖先组件</h3>
    <Parent />
  </div>
</template>

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

// 提供基本类型数据
const theme = ref('dark')
provide('theme', theme)

// 提供对象类型数据
const globalConfig = reactive({
  fontSize: '16px',
  color: '#333'
})
provide('globalConfig', globalConfig)

// 提供方法(支持双向通信)
provide('changeTheme', (newTheme) => {
  theme.value = newTheme
})
</script>

孙组件(Child.vue)

js 复制代码
<template>
  <div class="child">
    <h4>我是孙组件</h4>
    <p>祖先组件提供的主题:{{ theme }}</p>
    <p>全局配置:{{ globalConfig.fontSize }} / {{ globalConfig.color }}</p>
    <button @click="changeTheme('light')">切换为亮色主题</button>
  </div>
</template>

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

// 注入祖先组件提供的数据(第二个参数是默认值)
const theme = inject('theme', 'light')
const globalConfig = inject('globalConfig', {})
const changeTheme = inject('changeTheme', () => {})
</script>

2. Vuex/Pinia(全局状态管理)

当多个不相关的组件需要共享状态,或者项目规模较大时,推荐使用官方的状态管理库,Vue3 中更推荐 Pinia(比 Vuex 更简洁)。

示例:Pinia 实现全局通信

1. 安装 Pinia

js 复制代码
npm install pinia

2. 创建 Pinia 实例(main.js)

js 复制代码
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const app = createApp(App)
app.use(createPinia())
app.mount('#app')

3. 创建 Store(stores/user.js)

js 复制代码
import { defineStore } from 'pinia'

// 定义并导出store
export const useUserStore = defineStore('user', {
  // 状态
  state: () => ({
    username: '默认用户名',
    token: ''
  }),
  // 计算属性
  getters: {
    // 处理用户名格式
    formatUsername: (state) => {
      return `【${state.username}】`
    }
  },
  // 方法(修改状态)
  actions: {
    // 更新用户信息
    updateUserInfo(newInfo) {
      this.username = newInfo.username
      this.token = newInfo.token
    },
    // 清空用户信息
    clearUserInfo() {
      this.username = ''
      this.token = ''
    }
  }
})

4. 组件中使用 Store

js 复制代码
<template>
  <div>
    <h3>全局状态管理示例</h3>
    <p>用户名:{{ userStore.formatUsername }}</p>
    <p>Token:{{ userStore.token }}</p>
    <button @click="updateUser">更新用户信息</button>
    <button @click="clearUser">清空用户信息</button>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'

// 获取store实例
const userStore = useUserStore()

// 更新用户信息
const updateUser = () => {
  userStore.updateUserInfo({
    username: '掘金用户',
    token: '123456789'
  })
}

// 清空用户信息
const clearUser = () => {
  userStore.clearUserInfo()
}
</script>

三、其他常用通信方式

1. v-model 双向绑定

Vue3 中 v-model 支持自定义绑定属性,可实现父子组件的双向数据绑定,简化子传父的操作。

子组件(Child.vue)

js 复制代码
<template>
  <div class="child">
    <input 
      type="text" 
      :value="modelValue" 
      @input="emit('update:modelValue', $event.target.value)"
    />
    <!-- 支持多个v-model -->
    <input 
      type="number" 
      :value="age" 
      @input="emit('update:age', $event.target.value)"
    />
  </div>
</template>

<script setup>
defineProps(['modelValue', 'age'])
const emit = defineEmits(['update:modelValue', 'update:age'])
</script>

父组件(Parent.vue)

js 复制代码
<template>
  <div class="parent">
    <h3>父组件</h3>
    <Child 
      v-model="username"
      v-model:age="userAge"
    />
    <p>用户名:{{ username }}</p>
    <p>年龄:{{ userAge }}</p>
  </div>
</template>

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

const username = ref('')
const userAge = ref(0)
</script>

2. 事件总线(mitt)

Vue3 移除了 Vue2 的 $on/$emit 事件总线,可使用第三方库 mitt 实现任意组件间的通信。

1. 安装 mitt

复制代码
npm install mitt

2. 创建事件总线(utils/bus.js)

js 复制代码
import mitt from 'mitt'
const bus = mitt()
export default bus

3. 组件 A 发送事件

js 复制代码
<template>
  <div>
    <button @click="sendMsg">发送消息到组件B</button>
  </div>
</template>

<script setup>
import bus from '@/utils/bus'

const sendMsg = () => {
  // 触发自定义事件并传递数据
  bus.emit('msgEvent', '来自组件A的消息')
}
</script>

4. 组件 B 接收事件

js 复制代码
<template>
  <div>
    <p>组件A传递的消息:{{ msg }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import bus from '@/utils/bus'

const msg = ref('')

// 挂载时监听事件
onMounted(() => {
  bus.on('msgEvent', (data) => {
    msg.value = data
  })
})

// 卸载时移除监听(避免内存泄漏)
onUnmounted(() => {
  bus.off('msgEvent')
})
</script>

四、通信方式选型建议

表格

通信场景 推荐方式
父传子 Props
子传父 自定义事件(Emits)/v-model
爷孙 / 跨层级 provide / inject
全局共享状态 Pinia
任意组件临时通信 mitt 事件总线

总结

  1. Vue3 中父子组件通信优先使用 Props + Emits,v-model 可简化双向绑定场景;
  2. 跨层级通信推荐 provide / inject ,全局状态管理首选 Pinia
  3. 临时的任意组件通信可使用 mitt 事件总线,注意及时移除监听避免内存泄漏。

组件通信的核心是 "数据流向清晰",无论选择哪种方式,都要保证数据的传递路径可追溯,避免滥用全局通信导致代码维护困难。希望本文能帮助你彻底掌握 Vue3 组件通信,少走弯路~

相关推荐
子兮曰19 小时前
async/await高级模式:async迭代器、错误边界与并发控制
前端·javascript·github
恋猫de小郭19 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
GIS之路21 小时前
ArcGIS Pro 中的 Notebooks 入门
前端
IT_陈寒1 天前
React状态管理终极对决:Redux vs Context API谁更胜一筹?
前端·人工智能·后端
lemon_yyds1 天前
《vue 2 升级vue3 父组件 子组件 传值: value 和 v-model
vue.js
Kagol1 天前
TinyVue 支持 Skills 啦!现在你可以让 AI 使用 TinyVue 组件搭建项目
前端·agent·ai编程
柳杉1 天前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
simple_lau1 天前
Cursor配置MasterGo MCP:一键读取设计稿生成高还原度前端代码
前端·javascript·vue.js
睡不着先生1 天前
如何设计一个真正可扩展的表单生成器?
前端·javascript·vue.js
天蓝色的鱼鱼1 天前
模块化与组件化:90%的前端开发者都没搞懂的本质区别
前端·架构·代码规范