vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vue defineProps和defineEmits

Vue中defineProps和defineEmits的使用小结

作者: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(不再踩坑)

五、一句话总结

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

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

您可能感兴趣的文章:
阅读全文