Vue 3 父子组件通信机制解析
项目概述
本项目实现了一个典型的父子组件通信场景:父组件(App.vue)管理全局状态,两个子组件(TabHeader.vue 和 TabContent.vue)分别负责导航和内容展示。
组件架构
App.vue (父组件)
├── TabHeader.vue (子组件 - 导航)
└── TabContent.vue (子组件 - 内容)
通信机制详解
1. 父传子通信(Props 下传)
1.1 定义 Props(子组件)
TabHeader.vue:
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
activeTab: {
type: String,
required: true,
default: 'build'
}
})
</script>
<template>
<div class="tab-header">
<div
v-for="tab in tabs"
:key="tab.id"
:class="['tab-item', { active: props.activeTab === tab.id }]"
>
{{ tab.label }}
</div>
</div>
</template>
TabContent.vue:
<script setup>
import { defineProps } from 'vue'
const props = defineProps({
activeTab: {
type: String,
required: true,
default: 'build'
}
})
</script>
<template>
<div class="tab-content">
<div v-if="props.activeTab === 'build'" class="content-panel">
<h2>镜像构建</h2>
</div>
</div>
</template>
1.2 传递 Props(父组件)
App.vue:
<script setup>
import { ref } from 'vue'
import TabHeader from './components/TabHeader/TabHeader.vue'
import TabContent from './components/TabContent/TabContent.vue'
const activeTab = ref('build')
</script>
<template>
<div class="container">
<div class="box box-a">
<TabHeader :active-tab="activeTab" />
</div>
<div class="box box-b">
<TabContent :active-tab="activeTab" />
</div>
</div>
</template>
关键点:
- 使用
:active-tab="activeTab"语法传递数据 - 父组件的
activeTab是响应式数据(ref) - 子组件通过
props.activeTab访问数据
2. 子传父通信(Events 上传)
2.1 定义和触发事件(子组件)
TabHeader.vue:
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['tab-change'])
const handleTabClick = (tabId) => {
emit('tab-change', tabId)
}
</script>
<template>
<div class="tab-header">
<div
v-for="tab in tabs"
:key="tab.id"
@click="handleTabClick(tab.id)"
>
{{ tab.label }}
</div>
</div>
</template>
关键点:
- 使用
defineEmits(['tab-change'])声明可触发的事件 - 使用
emit('tab-change', tabId)触发事件并传递参数 - 事件名使用 kebab-case(短横线命名)
2.2 监听事件(父组件)
App.vue:
<script setup>
import { ref } from 'vue'
const activeTab = ref('build')
const handleTabChange = (tabId) => {
activeTab.value = tabId
}
</script>
<template>
<div class="container">
<div class="box box-a">
<TabHeader :active-tab="activeTab" @tab-change="handleTabChange" />
</div>
</div>
</template>
关键点:
- 使用
@tab-change="handleTabChange"监听子组件事件 - 事件名与子组件
emit的事件名对应 - 父组件通过参数接收子组件传递的数据
完整通信流程
3.1 初始状态
App.vue: activeTab = 'build'
↓ (props 下传)
TabHeader.vue: props.activeTab = 'build'
TabContent.vue: props.activeTab = 'build'
3.2 用户交互
用户点击 "镜像下载" 标签
↓
TabHeader.vue: handleTabClick('download')
↓ (emit 事件)
emit('tab-change', 'download')
↓ (事件上传)
App.vue: handleTabChange('download')
↓ (更新状态)
activeTab.value = 'download'
3.3 状态同步
App.vue: activeTab = 'download'
↓ (props 下传)
TabHeader.vue: props.activeTab = 'download' (重新渲染,激活状态更新)
TabContent.vue: props.activeTab = 'download' (重新渲染,内容更新)
通信机制特点
4.1 单向数据流
- 父传子:通过 props 单向传递,子组件不能直接修改 props
- 子传父:通过 events 通知父组件,由父组件决定如何处理
4.2 响应式更新
- 父组件状态变化自动触发子组件重新渲染
- Vue 的响应式系统确保 UI 始终与数据同步
4.3 组件解耦
- 子组件不需要知道父组件的具体实现
- 父组件不需要知道子组件的内部逻辑
- 通过 props 和 events 建立清晰的接口
最佳实践
5.1 Props 定义
const props = defineProps({
activeTab: {
type: String, // 类型检查
required: true, // 必填验证
default: 'build' // 默认值
}
})
5.2 Events 定义
const emit = defineEmits(['tab-change'])
const handleTabClick = (tabId) => {
emit('tab-change', tabId) // 事件名使用 kebab-case
}