编程 Vue3 组件通信详解:父传子、子传父与兄弟组件通信

2025-03-21 08:45:59 +0800 CST views 413

Vue3 组件通信详解:父传子、子传父与兄弟组件通信

前言

在 Vue3 中,组件之间的通信是构建复杂应用程序的基础。组件通信主要包括父传子子传父以及兄弟组件通信。本文将详细讲解这三种通信方式,并通过代码示例进行演示。


1. 父传子通信(Props)

父传子通信是 Vue 中最基本也是最常用的通信方式。父组件通过 props 向子组件传递数据。

示例代码

父组件(ParentComponent.vue)

<template>
  <div>
    <child-component :message="parentMessage" :userInfo="userInfo"></child-component>
  </div>
</template>

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

const parentMessage = ref('Hello from Parent');
const userInfo = reactive({ name: 'John', age: 30 });
</script>

子组件(ChildComponent.vue)

<template>
  <div>
    <p>{{ message }}</p>
    <p>{{ userInfo.name }}</p>
  </div>
</template>

<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  message: String,
  userInfo: Object,
});
</script>

说明

  • 父组件通过 v-bind(简写为 :)将 parentMessageuserInfo 数据绑定到子组件的 props 上。
  • 子组件通过 defineProps 接收这些属性,并在模板中使用。

2. 子传父通信(Emit)

子组件可以通过触发事件的方式向父组件传递数据。Vue3 中,子组件使用 $emit 方法触发事件,父组件通过监听这些事件来接收数据。

示例代码

父组件(ParentComponent.vue)

<template>
  <div>
    <child-component @update="handleUpdate" @delete="handleDelete"></child-component>
  </div>
</template>

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

const handleUpdate = (data) => {
  console.log('Received from child:', data);
};

const handleDelete = () => {
  // 处理删除逻辑
};
</script>

子组件(ChildComponent.vue)

<template>
  <div>
    <button @click="handleClick">更新父组件</button>
  </div>
</template>

<script setup>
import { defineEmits } from 'vue';

const emit = defineEmits(['update', 'delete']);

const handleClick = () => {
  emit('update', { id: 1, value: 'new value' });
};
</script>

说明

  • 子组件通过 defineEmits 定义可以触发的事件。
  • 在按钮点击事件中调用 emit 方法触发 update 事件,并传递数据给父组件。
  • 父组件通过 v-on(简写为 @)监听 updatedelete 事件,并定义相应的事件处理函数。

3. 兄弟组件通信

兄弟组件之间的通信通常需要通过一个共同父组件来中转,或者使用事件总线(如 mitt 库)来实现。

3.1 通过父组件中转

父组件(ParentComponent.vue)

<template>
  <div>
    <sibling-a @message="handleMessage" />
    <sibling-b :message="message" />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import SiblingA from './SiblingA.vue';
import SiblingB from './SiblingB.vue';

const message = ref('');

const handleMessage = (value) => {
  message.value = value;
};
</script>

兄弟组件 A(SiblingA.vue)

<template>
  <button @click="sendMessage">发送消息</button>
</template>

<script setup>
import { defineEmits } from 'vue';

const emit = defineEmits(['message']);

const sendMessage = () => {
  emit('message', 'Hello from sibling A');
};
</script>

兄弟组件 B(SiblingB.vue)

<template>
  <p>{{ message }}</p>
</template>

<script setup>
import { defineProps } from 'vue';

const props = defineProps(['message']);
</script>

说明

  • 兄弟组件 A 通过触发 message 事件向父组件发送数据。
  • 父组件接收到数据后更新自己的状态,并将状态通过 props 传递给兄弟组件 B。

3.2 使用事件总线(mitt)

安装 mitt 库

npm install mitt

main.js 中全局配置事件总线

import { createApp } from 'vue';
import mitt from 'mitt';
import App from './App.vue';

const app = createApp(App);
app.config.globalProperties.$bus = mitt();
app.mount('#app');

组件 A(ComponentA.vue)

<template>
  <button @click="sendMessage">发送消息</button>
</template>

<script setup>
import { getCurrentInstance } from 'vue';

const { proxy } = getCurrentInstance();

const sendMessage = () => {
  proxy.$bus.emit('myEvent', 'Hello from ComponentA');
};
</script>

组件 B(ComponentB.vue)

<script setup>
import { onMounted, getCurrentInstance } from 'vue';

const { proxy } = getCurrentInstance();

onMounted(() => {
  proxy.$bus.on('myEvent', (message) => {
    console.log(message); // 输出: "Hello from ComponentA"
  });
});
</script>

说明

  • 组件 A 通过事件总线发送消息,组件 B 通过事件总线接收消息。
  • 这种方式不需要通过父组件中转,实现了兄弟组件之间的直接通信。

3.3 全局状态管理(Pinia)

通过集中式状态管理库(如 Pinia)共享全局状态。

创建 Pinia Store

// stores/message.js
import { defineStore } from 'pinia';

export const useMessageStore = defineStore('message', {
  state: () => ({
    content: '',
  }),
  actions: {
    setContent(newContent) {
      this.content = newContent;
    },
  },
});

组件 A(ComponentA.vue)

<template>
  <button @click="update">更新全局状态</button>
</template>

<script setup>
import { useMessageStore } from '@/stores/message';
const store = useMessageStore();

const update = () => {
  store.setContent('全局消息内容');
};
</script>

组件 B(ComponentB.vue)

<template>
  <div>{{ store.content }}</div>
</template>

<script setup>
import { useMessageStore } from '@/stores/message';
const store = useMessageStore();
</script>

3.4 Provide/Inject + 响应式数据

通过祖先组件提供响应式数据,后代组件注入使用。

祖先组件(Ancestor.vue)

<template>
  <ComponentA />
  <ComponentB />
</template>

<script setup>
import { provide, ref } from 'vue';

const sharedData = ref('初始数据');
const updateData = (newValue) => {
  sharedData.value = newValue;
};

provide('sharedContext', {
  sharedData,
  updateData,
});
</script>

组件 A(ComponentA.vue)

<template>
  <button @click="update">修改共享数据</button>
</template>

<script setup>
import { inject } from 'vue';
const { updateData } = inject('sharedContext');

const update = () => {
  updateData('新数据');
};
</script>

组件 B(ComponentB.vue)

<template>
  <div>{{ sharedData }}</div>
</template>

<script setup>
import { inject } from 'vue';
const { sharedData } = inject('sharedContext');
</script>

3.5 共享响应式对象

创建独立的响应式对象文件供组件导入。

创建共享状态文件

// sharedState.js
import { reactive } from 'vue';

export const state = reactive({
  value: '',
  setValue(newVal) {
    this.value = newVal;
  },
});

组件 A(ComponentA.vue)

<template>
  <input v-model="state.value" />
</template>

<script setup>
import { state } from './sharedState';
</script>

组件 B(ComponentB.vue)

<template>
  <div>{{ state.value }}</div>
</template>

<script setup>
import { state } from './sharedState';
</script>

3.6 组件实例引用(ref)

通过 ref 直接访问组件实例方法。

父组件(Parent.vue)

<template>
  <ComponentA ref="compA" />
  <ComponentB :trigger="compA?.method" />
</template>

<script setup>
import { ref } from 'vue';
const compA = ref(null);
</script>

组件 A(ComponentA.vue)

<script setup>
const method = () => {
  console.log('组件A的方法被调用');
};

defineExpose({ method }); // 必须暴露方法
</script>

组件 B(ComponentB.vue)

<template>
  <button @click="trigger">调用方法</button>
</template>

<script setup>
defineProps(['trigger']);
</script>

方法对比与选择建议

方法优点缺点推荐场景
Props + 事件官方推荐,数据流清晰层级深时繁琐简单父子通信
Pinia专业状态管理,响应式完善需要学习成本中大型应用
事件总线完全解耦,灵活性强需手动管理事件监听临时事件通信
Provide/Inject跨层级通信高效数据流向不够直观深层嵌套组件
共享对象实现简单难以维护小型项目原型开发
组件实例引用直接访问方法破坏组件封装性特殊场景应急使用

总结

Vue3 中的组件通信方式多种多样,包括:

  • 父传子(Props)
  • 子传父(Emit)
  • 兄弟组件通信(通过父组件中转、事件总线 mitt、全局状态管理 Pinia、Provide/Inject + 响应式数据、共享响应式对象、组件实例引用)

每种方式都有其优缺点和适用场景,如 Props + 事件 适合简单父子通信,Pinia 适合中大型应用,事件总线 灵活但需手动管理。选择通信方式时需根据具体需求和应用规模进行权衡。

复制全文 生成海报 Vue 前端开发 组件

推荐文章

PHP设计模式:单例模式
2024-11-18 18:31:43 +0800 CST
如何开发易支付插件功能
2024-11-19 08:36:25 +0800 CST
Vue3中的v-model指令有什么变化?
2024-11-18 20:00:17 +0800 CST
H5抖音商城小黄车购物系统
2024-11-19 08:04:29 +0800 CST
使用Rust进行跨平台GUI开发
2024-11-18 20:51:20 +0800 CST
一键配置本地yum源
2024-11-18 14:45:15 +0800 CST
Vue中的异步更新是如何实现的?
2024-11-18 19:24:29 +0800 CST
MySQL 日志详解
2024-11-19 02:17:30 +0800 CST
PHP 压缩包脚本功能说明
2024-11-19 03:35:29 +0800 CST
纯CSS实现3D云动画效果
2024-11-18 18:48:05 +0800 CST
一文详解回调地狱
2024-11-19 05:05:31 +0800 CST
robots.txt 的写法及用法
2024-11-19 01:44:21 +0800 CST
软件定制开发流程
2024-11-19 05:52:28 +0800 CST
一键压缩图片代码
2024-11-19 00:41:25 +0800 CST
18个实用的 JavaScript 函数
2024-11-17 18:10:35 +0800 CST
imap_open绕过exec禁用的脚本
2024-11-17 05:01:58 +0800 CST
JavaScript中设置器和获取器
2024-11-17 19:54:27 +0800 CST
WebSocket在消息推送中的应用代码
2024-11-18 21:46:05 +0800 CST
Vue3中如何处理组件的单元测试?
2024-11-18 15:00:45 +0800 CST
地图标注管理系统
2024-11-19 09:14:52 +0800 CST
mendeley2 一个Python管理文献的库
2024-11-19 02:56:20 +0800 CST
宝塔面板 Nginx 服务管理命令
2024-11-18 17:26:26 +0800 CST
Hypothesis是一个强大的Python测试库
2024-11-19 04:31:30 +0800 CST
程序员茄子在线接单