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

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


一、defineProps:接收父组件传值

1. 非 TypeScript 环境
  • 基本用法
    • 通过对象形式定义属性,支持类型声明、默认值和验证规则。
    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) 基础类型定义(使用泛型)
<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 辅助函数。

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. 定义事件
  • 简单定义(无类型):
<script setup>
const emit = defineEmits(['onClick', 'onChange'])

// 触发事件
const handleClick = () => {
  emit('onClick', '来自子组件的数据')
}
</script>
  • TS 类型形式(推荐):
    const emit = defineEmits<{
      (e: 'onClick', name: string): void;  // 带参数的事件
      (e: 'update:count', value: number): void;
       // 可定义多个事件,格式:(事件名, 参数1, 参数2) => void
    }>();
    
2. 触发事件与父组件监听
  • 子组件触发:通过 emit() 传递数据。
    <button @click="emit('onClick', '你好啊')">传递值</button>
    
  • 父组件监听:使用 @事件名 接收数据。
    <ChildComponent @onClick="handleClick" />
    
    const handleClick = (name: string) => {
      console.log(`收到子组件值:${name}`);  // 输出:你好啊
    };
    

✅ 优势:

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


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

1. 子组件暴露内容
  • 通过 defineExpose 显式暴露属性或方法:
    // 子组件
    const name = 'neon';
    const open = () => console.log('执行 open 方法');
    
    defineExpose({
      name,
      open
    });
    
2. 父组件调用暴露内容
  • 定义 Ref 引用:为子组件实例声明类型。
    // 父组件
    import ChildComponent from './ChildComponent.vue';
    const childRef = ref<InstanceType<typeof ChildComponent>>();
    
  • 通过 Ref 调用
    <ChildComponent ref="childRef" />
    <button @click="handleCallChild">调用子组件</button>
    
    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 组件)

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

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

弹窗控制(Modal / Dialog)

// 子组件
defineExpose({
  open,
  close,
  toggle
})

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


四、对比 Vue 2 与 Vue 3

特性Vue 2Vue 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') 实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值