Vue中defineProps和defineEmits的使用小结

 更新时间:2026年08月17日 09:42:00   作者:JJCTO袁龙  
本文用大白话讲透 defineProps 和 defineEmits的用法,涵盖运行时声明、TS 泛型、v-model 双向绑定等高频场景,附万能模板和避坑清单,让你彻底告别组件通信的坑

一句话总结:defineProps 声明组件接收哪些 props,defineEmits 声明组件能触发哪些事件,它们是 <script setup> 的编译时宏,不需要 import,但必须搭配运行时校验和类型标注才能用得稳!

一、defineProps 和 defineEmits 是什么?

问题背景:Vue3 <script setup> 语法糖中,组件的 props 和 emits 不再通过 props/emits 选项声明,而是用 defineProps 和 defineEmits 宏来声明。

特性Options APIscript setup 宏
声明 propsprops: { ... }defineProps({ ... })
声明 emitsemits: [...]defineEmits([...])
需要 import不需要不需要(编译宏)
类型标注支持 TS withDefaults
默认值defaultwithDefaults 或运行时 default
<!-- ❌ 错误:在 script setup 中手动 import defineProps -->
<script setup>
import { defineProps } from 'vue'  // ❌ 不需要也不能 import!
defineProps({ msg: String })
</script>

二、5 大高频场景 & 代码示例

场景 1:基本类型校验 → 运行时声明

<!-- ✅ 正确:运行时声明,带类型和默认值 -->
<script setup>
const props = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0
  },
  items: {
    type: Array,
    default: () => []     // 对象/数组默认值必须用工厂函数
  },
  callback: {
    type: Function,
    default: () => () => {}
  }
})
</script>

<template>
  <h1>{{ title }}</h1>
  <p>共 {{ count }} 项</p>
</template>
<!-- ❌ 错误:对象默认值直接写对象 -->
<script setup>
const props = defineProps({
  config: {
    type: Object,
    default: { theme: 'dark' }  // ❌ 每个实例共享同一引用!
  }
})
</script>

场景 2:TypeScript 类型标注 → 泛型声明

<!-- ✅ 正确:TS 泛型声明,类型推断完整 -->
<script setup lang="ts">
interface Props {
  title: string
  count?: number          // 可选
  items?: string[]
}

const props = defineProps<Props>()

// withDefaults 提供默认值
const props2 = withDefaults(defineProps<Props>(), {
  count: 0,
  items: () => ['默认项']
})
</script>
<!-- ❌ 错误:运行时声明和泛型声明混用 -->
<script setup lang="ts">
// 不能同时用 type 对象和泛型
const props = defineProps<Props & {
  extra: {
    type: String,    // ❌ 泛型里不能写运行时校验
    required: true
  }
}>()
</script>

场景 3:defineEmits 事件声明 → 带校验

<!-- ✅ 正确:声明事件 + 校验函数 -->
<script setup>
const emit = defineEmits({
  // 校验函数
  submit: (payload) => {
    if (!payload.email.includes('@')) {
      console.warn('email 格式不对')
      return false
    }
    return true
  },
  cancel: null   // null 表示不校验
})

function handleSubmit() {
  emit('submit', { email: 'boss@example.com', name: '老板' })
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <button type="submit">提交</button>
    <button @click="emit('cancel')">取消</button>
  </form>
</template>
<!-- ❌ 错误:没声明 emit,直接用 $emit(setup 中没有 $emit) -->
<script setup>
function handleClick() {
  $emit('submit', data)  // ❌ ReferenceError: $emit is not defined
}
</script>
<!-- ✅ 正确:TS 泛型声明 emits -->
<script setup lang="ts">
const emit = defineEmits<{
  (e: 'submit', payload: { email: string; name: string }): void
  (e: 'cancel'): void
}>()
</script>

场景 4:v-model 双向绑定 → defineProps + defineEmits 配合

<!-- ✅ 正确:自定义组件实现 v-model -->
<!-- MyInput.vue -->
<script setup>
const props = defineProps<{
  modelValue: string
}>()

const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
}>()

function onInput(e) {
  emit('update:modelValue', e.target.value)
}
</script>

<template>
  <input :value="modelValue" @input="onInput" />
</template>
<!-- 父组件使用 -->
<template>
  <MyInput v-model="text" />
  <!-- 等价于 -->
  <MyInput :model-value="text" @update:model-value="text = $event" />
</template>
<!-- ✅ 进阶:多个 v-model -->
<script setup>
const props = defineProps<{
  modelValue: string
  title: string
}>()

const emit = defineEmits<{
  (e: 'update:modelValue', v: string): void
  (e: 'update:title', v: string): void
}>()
</script>

<!-- 父组件 -->
<template>
  <MyInput v-model="text" v-model:title="titleText" />
</template>

场景 5:defineExpose → 暴露方法给父组件 ref

<!-- ✅ 正确:defineExpose 暴露指定方法,不暴露内部实现 -->
<!-- ChildForm.vue -->
<script setup>
import { ref } from 'vue'

const formData = ref({ name: '', age: 0 })

// 内部方法,不暴露
function validate() {
  return formData.value.name.length > 0
}

// 只暴露需要的方法
defineExpose({
  validate,
  getData: () => formData.value,
  reset: () => { formData.value = { name: '', age: 0 } }
})
</script>
<!-- 父组件 -->
<script setup>
import { ref, onMounted } from 'vue'
import ChildForm from './ChildForm.vue'

const formRef = ref()

function handleSubmit() {
  if (formRef.value?.validate()) {
    const data = formRef.value.getData()
    console.log(data)
  }
}
</script>

<template>
  <ChildForm ref="formRef" />
  <button @click="handleSubmit">提交</button>
</template>
<!-- ❌ 错误:没 defineExpose,父组件 ref 拿不到任何东西 -->
<script setup>
function validate() { return true }
// 忘了 defineExpose
// 父组件 formRef.value.validate → undefined
</script>

三、万能兜底:Props/Emits 声明模板

场景推荐写法
简单项目运行时声明 defineProps({ x: String })
TS 项目泛型声明 defineProps<Props>() + withDefaults
事件校验defineEmits({ submit: (p) => true })
v-modelmodelValue prop + update:modelValue emit
多 v-model命名 v-model v-model:title
暴露方法defineExpose({ method })

万能模板:

<script setup lang="ts">
import { computed } from 'vue'

// 1. Props 泛型 + 默认值
interface Props {
  modelValue: string
  title?: string
  items?: string[]
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  title: '默认标题',
  items: () => [],
  disabled: false
})

// 2. Emits 泛型声明
const emit = defineEmits<{
  (e: 'update:modelValue', v: string): void
  (e: 'submit', payload: { value: string }): void
  (e: 'cancel'): void
}>()

// 3. computed 代理 v-model
const innerValue = computed({
  get: () => props.modelValue,
  set: (v) => emit('update:modelValue', v)
})

// 4. defineExpose 暴露方法
defineExpose({
  focus: () => {},
  reset: () => {}
})
</script>

四、预防 checklist(不再踩坑)

  • 不要 import defineProps / defineEmits,它们是编译宏
  • 对象/数组类型的 default 用工厂函数 () => ({}) / () => []
  • <script setup> 中没有 this,不能用 this.$emit,必须用 defineEmits 返回的 emit
  • 声明了 emits 的事件不会出现在 $attrs 上(不会被当原生事件)
  • v-model 的 prop 名叫 modelValue,事件名叫 update:modelValue
  • 多个 v-model 用具名 model v-model:title="titleText"
  • TS 项目优先用泛型声明 + withDefaults
  • 需要父组件 ref 调用子组件方法时,必须 defineExpose
  • emit 带校验函数时,返回 false 会触发警告但不阻止 emit

五、一句话总结

defineProps 声明接收、defineEmits 声明触发、defineExpose 声明暴露——三个编译宏撑起 <script setup> 的组件 API,搭配 TS 泛型和 withDefaults 才是完整形态。

到此这篇关于Vue中defineProps和defineEmits的使用小结的文章就介绍到这了,更多相关Vue defineProps和defineEmits内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Vue实现简易记事本

    Vue实现简易记事本

    这篇文章主要为大家详细介绍了Vue实现简易记事本,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • 基于Vue2-Calendar改进的日历组件(含中文使用说明)

    基于Vue2-Calendar改进的日历组件(含中文使用说明)

    这篇文章主要介绍了基于Vue2-Calendar改进的日历组件(含中文使用说明)的相关知识,非常不错,具有一定的参考借鉴价值 ,需要的朋友可以参考下
    2019-04-04
  • vue3如何使用postcss-px-to-viewport适配屏幕

    vue3如何使用postcss-px-to-viewport适配屏幕

    这篇文章主要介绍了vue3如何使用postcss-px-to-viewport适配屏幕问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • VUE 项目在IE11白屏报错 SCRIPT1002: 语法错误的解决

    VUE 项目在IE11白屏报错 SCRIPT1002: 语法错误的解决

    这篇文章主要介绍了VUE 项目在IE11白屏报错 SCRIPT1002: 语法错误的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • vue中的watch监听数据变化及watch中各属性的详解

    vue中的watch监听数据变化及watch中各属性的详解

    这篇文章主要介绍了vue中的watch监听数据变化及watch中的immediate、handler和deep属性详解,本文大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2018-09-09
  • 八种vue实现组建通信的方式

    八种vue实现组建通信的方式

    这篇文章主要介绍是八种vue实现组建通信的方式,包括、props 父组件与子组件通信、$emit 子组件父组件传递、$emit与props结合 兄弟组件传值等等,想具体了解的朋友可以参考下面文章的具体内容
    2021-09-09
  • vue3中的watch()的用法和具体作用

    vue3中的watch()的用法和具体作用

    这篇文章主要介绍了vue3中的watch()的用法和具体作用,通过合理和熟练使用watch()方法,开发者可以更加高效地完成前端开发工作,需要的朋友可以参考下
    2023-04-04
  • 使用vue-cli编写vue插件的方法

    使用vue-cli编写vue插件的方法

    本篇文章主要介绍了使用vue-cli编写vue插件的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-02-02
  • vue实现发表评论功能

    vue实现发表评论功能

    这篇文章主要为大家详细介绍了vue实现发表评论功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Vue.js刷新当前页面的常见方法

    Vue.js刷新当前页面的常见方法

    这篇文章主要介绍了Vue.js刷新当前页面的常见方法,文中通过代码示例讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-12-12

最新评论