Vue3.2里的defineProps咋用?从基础到实战全讲透!
做Vue3项目时,组件通信里“父传子”靠props,而Vue3.2在<script setup>语法里,用defineProps来声明props更简洁,但不少同学刚接触时,会疑惑“咋声明类型?默认值咋设?和TS咋结合?”今天把这些问题拆碎了讲明白。
defineProps是干啥的?和Vue2 props有啥不一样?
简单说,defineProps是Vue3给<script setup>语法专门设计的“声明props的工具”,以前Vue2写组件,得在export default里写props选项,现在用<script setup>更简洁——不用额外导入,直接在脚本里写defineProps就能声明父组件要传的参数。
举个对比:
Vue2选项式写法:
export default {
props: { String,
count: {
type: Number,
required: true
}
}
}
Vue3 <script setup>写法:
<script setup>
const props = defineProps({ String,
count: {
type: Number,
required: true
}
})
</script>
核心区别:
- Vue2的
props是选项式API的配置项;Vue3的defineProps是编译时宏(编译器自动处理,不用手动import)。 - 写法更内聚:Vue3把props声明直接放在
<script setup>里,和组件逻辑贴得更近,不用来回翻代码找props配置。
基础类型props咋声明?数组和对象写法有啥区别?
声明props分“数组写法”和“对象写法”,场景不同用法不同。
数组写法:简单场景快速声明
如果只需要声明props名称,不需要类型验证、默认值,用数组最方便:
const props = defineProps(['title', 'isShow', 'count'])
这种写法适合“父组件传啥类型都能接受”的简单场景,比如内部工具类组件,团队内约定好传值类型。
对象写法:精细控制props规则
如果要限制类型、设默认值、做必填校验,必须用对象写法,对象的key是props名称,value是配置项(类型、默认值、validator等)。
举个按钮组件的例子(限制类型+默认值):
const props = defineProps({
// 基础类型:直接写构造函数
btnText: String,
// 复杂配置:是否禁用、默认值
isDisabled: {
type: Boolean, // 限制传Boolean类型
default: false // 没传时默认false
},
// 必传参数
maxCount: {
type: Number,
required: true // 父组件必须传这个值
}
})
这里type可以是JS内置构造函数(String/Number/Boolean等),也能是自定义类(比如自己写的User类)。
复杂类型&自定义验证的props咋处理?
实际开发中,props经常是对象、数组,甚至需要“传的值必须满足某个规则”,这时候得用类型约束+验证函数。
场景1:对象类型props(带验证)
比如做用户信息卡片,需要父组件传user对象(包含id、name、avatar),为了防止传错值,加验证:
const props = defineProps({
user: {
type: Object,
required: true,
// 自定义验证函数:返回true才合法
validator: (user) => {
return user.id !== undefined && user.name && user.avatar
}
}
})
父组件如果传了没有id的user,Vue会在控制台报错,提前拦截错误。
场景2:数组类型props(带默认值)
如果props是数组,默认值必须用函数返回(否则所有组件实例会共享同一个数组,一个改了全乱套)。
标签列表”组件,默认展示空数组:
const props = defineProps({
tags: {
type: Array,
// 注意:对象/数组的默认值必须用函数返回新实例
default: () => []
}
})
要是写成default: [],多个组件实例会共享同一个空数组,某个实例push数据后,其他实例的tags也会被改——这是JS引用类型的“坑”,用函数返回新数组就能避免。
给props设置默认值有啥讲究?
默认值分“基本类型”和“复杂类型(对象/数组)”,写法不一样。
基本类型(字符串、数字、布尔):直接赋值
比如按钮组件的默认文案:
const props = defineProps({
btnText: {
type: String,
default: '提交' // 基本类型直接写默认值
}
})
复杂类型(对象、数组):必须用函数返回
前面讲数组时提过,对象同理,比如配置对象的默认值:
const props = defineProps({
config: {
type: Object,
// 函数返回新对象,避免引用共享
default: () => ({ theme: 'light', size: 'medium' })
}
})
如果直接写default: { theme: 'light' },所有用这个组件的地方,config的默认值都是同一个对象引用,只要有一个组件修改了config.theme,其他组件的默认值也会被改——这绝对是你不想看到的bug!
组件里咋访问defineProps声明的props?
defineProps返回的是响应式的props对象,直接用props.xxx访问就行。
比如模板里渲染:
<template>
<div class="card">
<h2>{{ props.title }}</h2>
<p>{{ props.description }}</p>
</div>
</template>
<script setup>
const props = defineProps(['title', 'description'])
// 脚本里也能直接用
console.log(props.title)
</script>
⚠️ 注意:不能直接解构props!比如const { title } = props,解构后title会变成普通变量,失去响应性(父组件更新title时,这里不会跟着变),如果非要解构,得用toRefs:
import { toRefs } from 'vue'
const props = defineProps(['title'])
const { title } = toRefs(props) // title变成响应式的ref
和TypeScript结合,defineProps咋写更优雅?
现在很多项目用TS,defineProps结合TS能让类型约束更严谨,核心思路是用接口(Interface)或类型别名(Type)定义props类型,再传给defineProps。
场景1:纯TS类型约束(无默认值)
比如文章组件需要title(必传字符串)和author(可选字符串):
interface ArticleProps { string;
author?: string; // 可选属性
}
const props = defineProps<ArticleProps>()
这样TS会在编译时检查:父组件传title必须是字符串,传author要么不传,传的话也得是字符串。
场景2:TS类型+默认值
如果要给可选属性设默认值,得用withDefaults宏(因为直接在defineProps对象里写default,和TS泛型结合会有类型冲突):
interface ArticleProps { string;
showCount?: number;
}
// withDefaults接收两个参数:defineProps<类型>() 和 默认值对象
const props = withDefaults(defineProps<ArticleProps>(), {
showCount: 5 // 没传showCount时,默认显示5条
})
withDefaults会自动推导类型,保证默认值和接口定义的类型一致,既严谨又方便。
用defineProps容易踩哪些坑?
看似简单的defineProps,实际开发稍不注意就会掉坑里,这几个“雷区”要避开:
坑1:解构props导致响应性丢失
前面提过,直接const { title } = props会让title变成非响应式,父组件更新title时,子组件里的title不会同步变化。
解决:要么直接用props.title,要么用toRefs解构。
坑2:直接修改props
props是父组件传的,子组件只能读,不能改!如果在子组件里写props.title = '新标题',Vue会直接报错。
正确做法:子组件要改props,得通过emit通知父组件,让父组件来修改(比如emit('update:title', '新标题'))。
坑3:运行时和类型检查混淆
TS的类型约束是“编译时检查”,如果父组件用JS写(没开TS检查),传错类型不会被发现,这时候运行时验证(对象写法里的type、validator)就很重要。
比如团队里有人用JS写父组件,给user传了个字符串,TS编译时不报错,但运行时type: Object的验证会触发报错,帮你拦截错误。
坑4:对象/数组默认值没写函数
前面反复强调:对象、数组的默认值必须用函数返回新实例,如果偷懒写成default: []或default: {},多个组件实例会共享同一个引用,出现“一个组件改了,其他组件跟着变”的诡异bug。
实际项目中,defineProps咋优化组件设计?
用好defineProps不止是“能跑就行”,更要让组件易读、易维护、少踩坑,分享几个实战技巧:
技巧1:清晰分类props
把props按“业务数据”和“UI配置”分开声明,代码结构更清晰。
const props = defineProps({
// 业务数据:用户信息
user: { type: Object, required: true },
// UI配置:卡片风格、是否显示边框
cardStyle: { type: String, default: 'normal' },
showBorder: { type: Boolean, default: true }
})
技巧2:用TS接口统一定义
复杂组件的props类型,单独用接口声明,放在组件顶部或单独文件里。
// 单独的types.ts文件
export interface UserCardProps {
user: { id: number; name: string; avatar: string };
showDesc: boolean;
maxLine?: number;
}
// 组件里导入使用
import { UserCardProps } from './types.ts'
const props = defineProps<UserCardProps>()
其他开发者看接口就知道组件需要啥参数,不用翻组件逻辑。
技巧3:合理用默认值和验证
- 默认值:减少父组件传参压力(比如大部分场景用默认风格,就给UI配置项设默认值)。
- 验证函数:复杂业务场景下,提前拦截非法值(比如用户id必须是数字,用
validator验证)。
技巧4:结合文档工具自动生成文档
如果用Storybook这类工具,defineProps声明的props能自动生成组件文档,比如给props加注释:
const props = defineProps({
/** 卡片标题 */ String,
/** 是否禁用按钮 */
isDisabled: { type: Boolean, default: false }
})
Storybook能读取这些注释,生成交互文档,团队协作效率翻倍。
吃透defineProps,Vue3组件通信的“父传子”就稳了,从基础声明到复杂场景,从TS结合到避坑技巧,核心是让props的声明更清晰、更严谨,实际项目里多练,遇到问题再回头看这些细节,组件设计能力自然就上去了~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网



