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

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

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

1. 父传子:Props

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

父组件(Parent.vue)

<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)

<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)

<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)

<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)

<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)

<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

npm install pinia

2. 创建 Pinia 实例(main.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)

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

<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)

<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)

<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)

import mitt from 'mitt'
const bus = mitt()
export default bus

3. 组件 A 发送事件

<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 接收事件

<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 组件通信,少走弯路~

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:alixiixcom@163.com