Vue 3 组件通信实战系列(一)父子组件通信的标准姿势:Props 与 Emit(含实战与进阶技巧)

Vue 3 组件通信实战系列(一)父子组件通信的标准姿势:Props 与 Emit(含实战与进阶技巧)

在 Vue 3 中,组件通信是项目开发的核心能力之一,而父子组件通信是最基础也是最常用的通信方式。

相比 Vue 2,Vue 3 带来了 Composition API、TypeScript 类型增强、<script setup> 等现代特性,使得父子通信的实践方式发生了显著变化,既更简洁,又更类型安全。

本篇文章将手把手带你掌握:

  • props:父传子数据的标准用法、类型定义与默认值技巧
  • emit:子传父事件的声明方法、参数校验与类型提示
  • 封装通信逻辑、提高复用性的方法
  • 常见踩坑场景与规范总结

一、父传子通信:Props 的标准与进阶用法

1.1 什么是 Props?

Props 是组件间通信中用于 从父组件向子组件传递数据 的方式。在 Vue 中,它们是单向绑定的,只能由父组件控制,子组件只能"接收"和"消费"。


1.2 基础使用案例

👨‍👩‍👧 父组件:传入数据
xml 复制代码
<!-- Parent.vue -->
<template>
  <Child :title="pageTitle" :count="counter" />
</template>

<script setup>
import Child from './Child.vue'

const pageTitle = '欢迎来到 Vue 组件通信世界'
const counter = 3
</script>
👶 子组件:接收 Props
xml 复制代码
<!-- Child.vue -->
<script setup>
defineProps(['title', 'count'])
</script>

<template>
  <h2>{{ title }}</h2>
  <p>当前数量:{{ count }}</p>
</template>

🔎 **注意:**这是一种快速声明方式,适合原型开发。但推荐在正式开发中使用更安全的方式:泛型 + 类型定义


1.3 推荐方式:使用 TypeScript 泛型定义 Props 类型

xml 复制代码
<script setup lang="ts">
defineProps<{
  title: string
  count: number
}>()
</script>

优点:

  • 明确参数类型
  • 支持 IDE 智能提示
  • 提高可维护性,降低低级错误发生概率

1.4 设置默认值:withDefaults 的正确用法

有时候,父组件可能不传某个 prop。为了保证子组件的健壮性,我们可以设置默认值。

xml 复制代码
<script setup lang="ts">
const props = withDefaults(defineProps<{
  title?: string
  count?: number
}>(), {
  title: '默认标题',
  count: 0
})
</script>

💡提示: 默认值用于可选属性(?),确保子组件不会因数据缺失而崩溃。


1.5 高阶示例:接收复杂对象、数组

typescript 复制代码
<script setup lang="ts">
interface UserInfo {
  name: string
  age: number
}

const props = withDefaults(defineProps<{
  user: UserInfo
}>(), {
  user: () => ({
    name: '匿名用户',
    age: 18
  })
})
</script>

<template>
  <p>{{ user.name }},{{ user.age }} 岁</p>
</template>

⚠️ 对象和数组默认值必须是函数返回,否则会出现多个组件实例共享同一引用的风险。


1.6 不能修改 Props:单向数据流约束

Vue 强调单向数据流。子组件不能直接修改 props:

arduino 复制代码
props.count++  // ❌ 会报错:Cannot assign to read only property

正确做法是:通过 emit 通知父组件更新(见下一节)。


二、子传父通信:Emit 的标准与类型安全用法

2.1 什么是 Emit?

子组件通过 emit 发出事件,父组件监听该事件并处理。这是 子组件向父组件传递消息或数据的标准方式


2.2 基础用法示例

👶 子组件
xml 复制代码
<script setup lang="ts">
const emit = defineEmits(['update-count'])

function update() {
  const newCount = Math.floor(Math.random() * 100)
  emit('update-count', newCount)
}
</script>

<template>
  <button @click="update">点击更新 Count</button>
</template>
👨‍👩‍👧 父组件
xml 复制代码
<template>
  <Child @update-count="handleCount" />
</template>

<script setup>
function handleCount(newCount: number) {
  console.log('子组件更新了 count:', newCount)
}
</script>

2.3 推荐方式:为 Emit 定义类型签名

typescript 复制代码
const emit = defineEmits<{
  (e: 'update-count', payload: number): void
}>()

优势:

  • 事件名提示
  • 参数类型校验
  • IDE 支持更完整

2.4 常见通信模式:子组件通知父组件修改数据

这是典型的**"子传父 -> 父传子"**模式,配合 v-model 使用更优雅(后续专题讲 v-model 双向绑定):

scss 复制代码
// 子组件内部触发修改
emit('update:modelValue', newValue)

配合父组件:

ini 复制代码
<Child v-model="value" />

三、封装通信逻辑:useXXX 模式的实战应用

在中大型项目中,通信逻辑可以通过组合式函数封装,提高代码复用与清晰度。

typescript 复制代码
// useDialog.ts
export function useDialog(props: { visible: boolean }, emit: (e: 'update:visible', v: boolean) => void) {
  const close = () => emit('update:visible', false)
  return { isVisible: props.visible, close }
}

在组件中:

arduino 复制代码
<script setup>
const props = defineProps<{ visible: boolean }>()
const emit = defineEmits<{
  (e: 'update:visible', v: boolean): void
}>()

const { isVisible, close } = useDialog(props, emit)
</script>

四、常见问题与错误用法

问题 原因 解决方案
props 值为只读 Vue 设计为单向数据流 使用 emit 通知父组件修改
defineEmits 不定义事件 会失去类型提示 显式声明每个事件和参数类型
默认值为对象但未使用函数 多组件共享同一引用 使用函数返回对象作为默认值
不使用泛型定义 props 缺乏类型提示 使用泛型进行明确声明

五、最佳实践与总结

  • 所有 propsemit 都应进行类型声明;
  • 推荐使用 <script setup> 结合 Composition API;
  • 使用 withDefaults 设置合理默认值;
  • props 为"读",emit 为"写",保持组件单向数据流;
  • 复用通信逻辑推荐抽离为组合式函数(useXXX)模块;

下一篇预告

在下一篇文章中,我们将深入探索「兄弟组件通信」的多种方式,包括:

  • mitt 全局事件总线的使用
  • provide/inject 跨层级传值
  • 中间组件代理通信

敬请期待:Vue 3 组件通信系列(二):兄弟组件如何优雅通信?


如果你觉得这篇文章对你有帮助:

👉 欢迎点赞、收藏、关注专栏

📌 持续更新「Vue 3 组件通信实战系列」

🧑‍💻 项目实战代码模板、专属源码包可私信获取


相关推荐
OpenTiny社区3 分钟前
盘点字体性能优化方案
前端·javascript
FogLetter7 分钟前
深入浅出React Hooks:useEffect那些事儿
前端·javascript
Savior`L7 分钟前
CSS知识复习4
前端·css
0wioiw022 分钟前
Flutter基础(前端教程④-组件拼接)
前端·flutter
花生侠1 小时前
记录:前端项目使用pnpm+husky(v9)+commitlint,提交代码格式化校验
前端
一涯1 小时前
Cursor操作面板改为垂直
前端
我要让全世界知道我很低调1 小时前
记一次 Vite 下的白屏优化
前端·css
1undefined21 小时前
element中的Table改造成虚拟列表,并封装成hooks
前端·javascript·vue.js
paopaokaka_luck1 小时前
基于SpringBoot+Vue的非遗文化传承管理系统(websocket即时通讯、协同过滤算法、支付宝沙盒支付、可分享链接、功能量非常大)
java·数据库·vue.js·spring boot·后端·spring·小程序