Vue 3 父子组件通信机制解析

0 阅读2分钟

Vue 3 父子组件通信机制解析

image.png

项目概述

本项目实现了一个典型的父子组件通信场景:父组件(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
}