Vue3 父子组件通信(附示例)

在 Vue3 中,父子组件通信是构建复杂应用的核心技能之一。本教程将详细介绍 5 种常用通信方式,并提供完整代码示例,助你快速掌握组件间的数据与事件交互。


一、父传子:Props 传递数据

父组件通过 Props 向子组件传递数据,子组件通过 defineProps 接收。
示例代码:

<!-- Parent.vue -->
<template>
  <Child :message="parentMsg" />
</template>

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

const parentMsg = ref('Hello from Parent!');
</script>

<!-- Child.vue -->
<template>
  <div>{{ message }}</div>
</template>

<script setup>
defineProps({
  message: {
    type: String,
    required: true
  }
});
</script>

关键点:

  • 父组件通过 :message 绑定动态数据
  • 子组件通过 defineProps 声明接收的 Props 类型

二、子传父:自定义事件(emit)

子组件通过 defineEmits 定义事件,父组件监听事件并处理数据。
示例代码:

<!-- Parent.vue -->
<template>
  <Child @child-event="handleEvent" />
  <p>收到子组件消息:{{ childMsg }}</p>
</template>

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

const handleEvent = (msg) => {
  childMsg.value = msg;
};
</script>

<!-- Child.vue -->
<template>
  <button @click="sendMsg">发送消息</button>
</template>

<script setup>
const emit = defineEmits(['child-event']);

const sendMsg = () => {
  emit('child-event', 'Hello from Child!');
};
</script>

关键点:

  • 子组件通过 emit() 触发事件并传参
  • 父组件通过 @child-event 监听事件

三、父访问子组件:ref + defineExpose

通过 ref 获取子组件实例,需配合 defineExpose 暴露方法/数据。
示例代码:

<!-- Parent.vue -->
<template>
  <Child ref="childRef" />
  <button @click="callChildMethod">调用子组件方法</button>
</template>

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

const callChildMethod = () => {
  childRef.value.showMessage(); // 调用子组件方法
  console.log(childRef.value.count); // 访问子组件数据
};
</script>

<!-- Child.vue -->
<script setup>
import { ref, defineExpose } from 'vue';

const count = ref(0);
const showMessage = () => {
  console.log('子组件方法被调用');
};

defineExpose({ showMessage, count }); // 暴露指定内容
</script>

关键点:

  • 子组件需通过 defineExpose 显式暴露内容
  • 父组件通过 ref.value 访问子组件实例

四、双向绑定:v-model 语法糖

通过 v-model 实现父子组件数据的双向绑定。
示例代码:

<!-- Parent.vue -->
<template>
  <Child v-model="inputValue" />
</template>

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

<!-- Child.vue -->
<template>
  <input 
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  >
</template>

<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
</script>

等价形式:
v-model:modelValue + @update:modelValue 的语法糖


五、批量传递:$attrs 透传

通过 $attrs 传递未在 Props 中声明的属性,常用于高阶组件封装。
示例代码:

<!-- Parent.vue -->
<Child class="custom-class" data-id="123" />

<!-- Child.vue -->
<template>
  <div v-bind="$attrs"> <!-- 透传所有未声明属性 -->
    {{ $attrs.data-id }}
  </div>
</template>

关键点:

  • $attrs 包含父组件传递的所有非 Props 属性
  • 通过 v-bind="$attrs" 实现属性透传

总结对比

方式 方向 适用场景
Props 父 → 子 传递初始数据、配置参数
Emit 子 → 父 触发父组件逻辑、提交表单数据
ref + expose 父 → 子 调用子组件方法/获取状态
v-model 双向绑定 表单控件、自定义输入组件
$attrs 父 → 子 高阶组件封装、属性透传

Vue3 父子组件通信补充:Slots(插槽)详解

在 Vue3 中,Slots(插槽) 是一种强大的父子组件通信方式,允许父组件向子组件传递模板片段,实现内容分发与动态布局。以下是 Slots 的三种核心用法及完整示例:


一、默认插槽(Default Slot)

作用:父组件向子组件传递通用内容模板,子组件通过 <slot> 标签接收。
适用场景:子组件作为容器,父组件自定义内部结构。

示例代码

<!-- Parent.vue -->
<template>
  <Child>
    <p>这是父组件传递的默认内容</p>
  </Child>
</template>

<!-- Child.vue -->
<template>
  <div class="child-container">
    <slot></slot> <!-- 默认插槽位置 -->
  </div>
</template>

关键点

  • 父组件内容会替换子组件中的 <slot> 标签

  • 支持设置默认内容(当父组件未传内容时显示):

    <slot>默认提示文字</slot>
    

二、具名插槽(Named Slots)

作用:父组件传递多个不同位置的模板,子组件通过 name 属性区分插槽位置。

示例代码

<!-- Parent.vue -->
<template>
  <Child>
    <template v-slot:header>
      <h2>标题区域</h2>
    </template>
    <template v-slot:content>
      <p>正文内容</p>
    </template>
  </Child>
</template>

<!-- Child.vue -->
<template>
  <div>
    <slot name="header"></slot>
    <slot name="content"></slot>
  </div>
</template>

简写语法

<template #header>...</template>

三、作用域插槽(Scoped Slots)

作用:子组件向父组件传递数据,父组件根据数据动态渲染内容。

示例代码

<!-- Parent.vue -->
<template>
  <Child>
    <template #item="slotProps">
      <span>{{ slotProps.text }} - {{ slotProps.index }}</span>
    </template>
  </Child>
</template>

<!-- Child.vue -->
<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">
      <slot name="item" :text="item" :index="index"></slot>
    </li>
  </ul>
</template>

<script setup>
const items = ['Apple', 'Banana', 'Orange'];
</script>

关键点

  • 子组件通过 <slot :data="value"> 传递数据
  • 父组件通过 v-slot:name="props" 接收数据
  • 支持解构语法:v-slot:item="{ text, index }"

四、动态插槽名(Dynamic Slots)

作用:通过动态指令参数实现插槽名的动态绑定

示例代码

<template>
  <Child>
    <template #[dynamicSlotName]>
      <!-- 动态内容 -->
    </template>
  </Child>
</template>

<script setup>
const dynamicSlotName = ref('header');
</script>

对比总结

类型 数据流向 核心能力
默认插槽 父 → 子 基础内容分发
具名插槽 父 → 子 多区域内容分发
作用域插槽 子 → 父 子组件数据反哺父组件渲染逻辑
动态插槽 双向 动态控制插槽位置

最佳实践

  1. 组件解耦:通过插槽实现 UI 与逻辑分离,提升组件复用性
  2. 组合式 API:在 <script setup> 中可直接使用插槽逻辑
  3. 性能优化:避免在插槽内容中使用复杂计算,必要时用 v-memo 缓存