Vue 3 父子组件通信核心机制详解:defineProps、defineEmits 与 defineExpose 完全指南

在 Vue 3 的组合式 API(Composition API)中,组件间的通信变得更加清晰、类型安全且易于维护。本文将系统梳理 definePropsdefineEmitsdefineExpose 三大核心 API 的使用方式,涵盖 JavaScript 与 TypeScript 环境下的差异,并结合实际场景说明。


一、defineProps:接收父组件传值

1. 非 TypeScript 环境
  • 基本用法

    • 通过对象形式定义属性,支持类型声明、默认值和验证规则。
    javascript 复制代码
    const props = defineProps({
      title: {
        type: String,        // 类型检查
        default: '默认标题',  // 默认值
        required: true       // 必填校验
      },
      count: {
        type: Number,
        validator: (value) => value >= 0  // 自定义验证
      }
    });
  • 模板使用 :直接通过属性名访问({``{ title }})。

  • JS 访问 :通过 props.title 引用 。

  • type :指定属性类型(String, Number, Boolean, Array, Object, Function, null 或自定义构造函数)

  • default:设置默认值

  • required:是否为必传项

  • validator:自定义验证函数(高级用法)

⚠️ 注意:简单类型(如字符串、数字)的 default 可直接赋值;复杂类型(对象、数组)必须使用函数返回,避免引用共享。


2. TypeScript 环境
(1) 基础类型定义(使用泛型)
typescript 复制代码
<script setup lang="ts">
interface Props {
  title: string
  list?: number[] // 可选属性
  userInfo: {
    name: string
    age: number
  }
}

const props = defineProps<Props>()
</script>
(2) 设置默认值:withDefaults

由于 defineProps 在 TS 中是类型推导,不能直接在类型中写默认值 ,因此需要使用 withDefaults 辅助函数。

typescript 复制代码
const props = withDefaults(
  defineProps<{
    title: string
    list?: number[]
    userInfo: {
      name: string
      age: number
    }
  }>(),
  {
    title: '默认标题',
    list: () => [1, 2, 3],
    userInfo: () => ({
      name: '小明',
      age: 18
    })
  }
)

关键点:

  • withDefaults 第一个参数必须是 defineProps() 的调用结果。
  • 所有默认值中,对象和数组必须用函数返回,防止多个组件实例共享同一引用。
  • 可选属性(?)也可以设置默认值。

二、defineEmits:子组件向父组件传值

1. 定义事件
  • 简单定义(无类型):
javascript 复制代码
<script setup>
const emit = defineEmits(['onClick', 'onChange'])

// 触发事件
const handleClick = () => {
  emit('onClick', '来自子组件的数据')
}
</script>
  • TS 类型形式 (推荐):

    typescript 复制代码
    const emit = defineEmits<{
      (e: 'onClick', name: string): void;  // 带参数的事件
      (e: 'update:count', value: number): void;
       // 可定义多个事件,格式:(事件名, 参数1, 参数2) => void
    }>();
2. 触发事件与父组件监听
  • 子组件触发 :通过 emit() 传递数据。

    html 复制代码
    <button @click="emit('onClick', '你好啊')">传递值</button>
  • 父组件监听 :使用 @事件名 接收数据。

    html 复制代码
    <ChildComponent @onClick="handleClick" />
    typescript 复制代码
    const handleClick = (name: string) => {
      console.log(`收到子组件值:${name}`);  // 输出:你好啊
    };

✅ 优势:

  • 编辑器自动提示事件名和参数
  • 调用 emit 时参数类型错误会立即报错
  • 支持多个事件定义


三、defineExpose:子组件暴露属性/方法

1. 子组件暴露内容
  • 通过 defineExpose 显式暴露属性或方法:

    typescript 复制代码
    // 子组件
    const name = 'neon';
    const open = () => console.log('执行 open 方法');
    
    defineExpose({
      name,
      open
    });
2. 父组件调用暴露内容
  • 定义 Ref 引用 :为子组件实例声明类型。

    typescript 复制代码
    // 父组件
    import ChildComponent from './ChildComponent.vue';
    const childRef = ref<InstanceType<typeof ChildComponent>>();
  • 通过 Ref 调用

    html 复制代码
    <ChildComponent ref="childRef" />
    <button @click="handleCallChild">调用子组件</button>
    typescript 复制代码
    const handleCallChild = () => {
      console.log(childRef.value?.name);  // 输出:neon
      childRef.value?.open();              // 输出:执行 open 方法
    };

InstanceType<typeof Component>:获取组件的实例类型,确保调用安全。


3. 典型应用场景

常见于 UI 组件库(如 Element Plus、Vuetify)中的表单、弹窗等组件:例如表单组件通过 defineExpose 暴露 validate()(验证)、reset()(重置)等方法,父组件通过 ref 调用这些方法实现表单操作。

表单验证(Form 组件)

typescript 复制代码
// 子组件:Form.vue
defineExpose({
  validate: (callback: (valid: boolean) => void) => { /* 验证逻辑 */ },
  reset: () => { /* 重置表单 */ }
})
typescript 复制代码
// 父组件
const formRef = ref<InstanceType<typeof Form>>()

const submit = () => {
  formRef.value?.validate((valid) => {
    if (valid) {
      console.log('表单验证通过,提交数据')
    }
  })
}

弹窗控制(Modal / Dialog)

typescript 复制代码
// 子组件
defineExpose({
  open,
  close,
  toggle
})

父组件可统一控制多个弹窗的显示隐藏,无需通过 v-modelprops 反复传递状态。


四、对比 Vue 2 与 Vue 3

特性 Vue 2 Vue 3
Props 定义 props 选项 defineProps 函数
类型支持 无 TS 推断 原生 TS 支持
默认值设置 default 属性 withDefaults(TS 专属)
事件定义 emits 选项 defineEmits + 类型声明
暴露方法 this.$refs 隐式访问 defineExpose 显式暴露

优势:Vue 3 的 Composition API 提供更清晰的类型安全性和代码组织能力 。


五、注意事项

  1. 优先使用 TypeScript
    • 获得完整的类型推导与错误提示。
  2. 复杂类型默认值用函数返回
    • 避免引用共享问题 。
  3. 事件命名规范
    • 事件命名使用小驼峰或 kebab-case(如 updateCount),父组件监听时可写为 @update-count
  4. Ref 安全访问
    • 调用 childRef.value 前需用可选链(?.)避免空值错误 。
  5. 结合 v-model 实现双向绑定
    • 基于 props + emit('update:xxx') 实现。
相关推荐
IT毕设实战小研11 分钟前
基于SpringBoot的救援物资管理系统 受灾应急物资管理系统 物资管理小程序
java·开发语言·vue.js·spring boot·小程序·毕业设计·课程设计
德育处主任1 小时前
p5.js 3D盒子的基础用法
前端·数据可视化·canvas
前端的阶梯1 小时前
为何我的figma-developer-mcp不可用?
前端
weixin_456904271 小时前
Vue3入口文件main.js解析
前端·javascript·vue.js
Awbeci1 小时前
微前端-解决MicroApp微前端内存泄露问题
前端
前端领航者2 小时前
重学Vue3《Vue Watch 监听器深度指南:场景、技巧与底层优化原理剖析》
前端·vue.js
布列瑟农的星空2 小时前
34岁老前端的一周学习总结(2025/8/15)
前端·后端
豆苗学前端2 小时前
vue3+TypeScript 实现一个图片占位符生成器
前端·面试·github
Ciito2 小时前
vue+moment将分钟调整为5的倍数(向下取整)
javascript·vue.js