一、withDefaults为啥会出现?解决了什么痛点?
不少刚接触Vue3的同学,在处理组件props默认值时,总会疑惑withDefaults这个API的作用——为啥不能像Vue2那样直接写default?它和旧写法有啥区别?今天用问答形式,把withDefaults的作用、用法、避坑点全讲透,帮你彻底掌握~
要理解withDefaults的出现,得先看Vue3的语法变化和对TypeScript的支持逻辑,在Vue2里,我们写props默认值是这样的:
// Vue2 写法 export default { props: { { type: String, default: '默认标题' }, list: { type: Array, default() { return [] } } } }
但到了Vue3的setup
语法糖中,我们用defineProps
来声明props,如果项目用TypeScript,还需要给props加类型约束,这时候问题来了:怎么在TS类型声明和默认值之间做平衡?
举个真实场景:之前团队做Vue3迁移,有个组件需要接收一个columns
数组(表格列配置),Vue2写法是在props里写default() { return [] }
,迁移到Vue3后,同学直接用defineProps<{ columns?: Column[] }>
,没加默认值,结果其他同事用组件时,忘记传columns,页面直接报“columns.map is not a function”——因为columns是undefined,没有默认值兜底,这时候withDefaults的价值就体现了:给columns设置默认值() => []
,既能用TS约束Column类型,又能保证不传时是空数组,避免运行时错误。
回到核心问题:Vue3中,defineProps
的泛型写法(纯TS类型声明)和运行时的默认值配置是“分离”的,如果想给TS类型约束的props加默认值,直接像Vue2那样写default
不支持,而withDefaults
就是为了解决“TS类型声明 + props默认值”这个场景诞生的API,它能让我们在给props加TS类型约束的同时,优雅地设置默认值,还能让TS自动推导“设置默认值后,props的可选性变化”(比如title原本是string | undefined
,设置默认值后变成string
)。
withDefaults基本用法长啥样?举个例子?
先看完整代码结构,再拆解每个部分:
<script setup lang="ts"> // 1. 用defineProps声明TS类型 const props = withDefaults( defineProps<{: string // 可选字符串 list?: number[] // 可选数字数组 isShow?: boolean // 可选布尔值 }>(), { '默认标题', // 基本类型默认值直接写 list: () => [], // 引用类型(数组)默认值用函数返回 isShow: false // 基本类型默认值直接写 } ) </script>
拆解关键点:
- 第一个参数:必须是
defineProps
的返回值,因为只有defineProps
声明的props,才能被Vue的响应式系统识别,withDefaults也只负责处理这类props的默认值。 - 第二个参数:是默认值对象,键要和defineProps里声明的props名字一一对应。
- 默认值的写法:和Vue2一样,基本类型(字符串、布尔、数字等)可以直接写值;引用类型(数组、对象等)必须用函数返回值,否则多个组件实例会共享同一个引用,导致状态互相污染(比如A组件改了list,B组件的list也会变)。
再举个🌰:如果list的默认值写成list: []
,所有使用这个组件的地方,list都会指向同一个数组,当某个组件修改list(比如push元素),其他组件的list也会被影响——这是Vue里引用类型默认值的经典坑,所以引用类型必须用函数返回新实例。
和Vue2的props默认值写法有啥不同?
直接对比写法和场景,更直观:
维度 | Vue2 写法 | Vue3 withDefaults 写法 |
---|---|---|
语法位置 | 在props 选项里,和type 、required 等平级配置 |
在setup 语法糖中,用defineProps +withDefaults 组合使用 |
TS 支持 | 需要额外用PropType 做类型断言,写法繁琐 |
直接通过defineProps<泛型> 做TS类型声明,和默认值结合更自然 |
默认值逻辑 | 引用类型默认值用default() { return [] } 函数 |
引用类型默认值同样需要函数返回(list: () => [] ),逻辑和Vue2一致 |
适用场景 | 非TS项目,或TS项目中“运行时props验证”优先级更高的场景 | TS项目中,需要“TS类型严格约束 + 简洁的默认值配置”的场景 |
简单说:Vue2的写法更偏向“运行时配置”,Vue3的withDefaults更偏向“TS类型优先的配置”,如果你的项目重度依赖TS,用withDefaults能让props的类型和默认值管理更简洁;如果是老项目迁移,或者更看重运行时的props验证(比如非TS项目),Vue2风格的写法也能继续用(Vue3兼容这种写法)。
用withDefaults要注意哪些细节?
这几个细节稍不注意就踩坑,得重点关注:
类型必须“对得上”
withDefaults的默认值类型,必须和defineProps里声明的类型兼容。
// 错误示例:类型不兼容 const props = withDefaults( defineProps<{ count?: number // 声明是number或undefined }>(), { count: '123' // 这里给了string,TS会直接报错 } )
TS会在编译阶段就提醒你“类型不匹配”,所以写的时候要保证默认值的类型,是defineProps声明类型的“子类型”。
引用类型默认值,必须用函数返回
前面提过,数组、对象这些引用类型,默认值不能直接写字面量,必须用函数返回新实例。
// 错误示例:共享引用 const props = withDefaults( defineProps<{ list?: number[] }>(), { list: [] // 所有组件实例的list都指向同一个数组! } )
正确写法是list: () => []
,这样每次组件实例化时,都会生成一个新的空数组,避免状态污染。
只能用在defineProps的返回值上
withDefaults是Vue3专门给props设计的API,不能用来处理普通对象。
// 错误示例:用在普通对象上 const customObj = { a: 1 } withDefaults(customObj, { a: 2 }) // 会报错!
Vue的编译和运行时逻辑,只认defineProps返回的props对象,所以withDefaults必须和defineProps配对使用。
TS严格模式下,props的“可选性”会变化
假设defineProps里声明title?: string
(title是可选的,类型是string | undefined
),但用withDefaults设置了默认值后,在组件内部使用props.title
时,TS会认为它的类型是string
(因为默认值兜底了,不可能是undefined),这种“可选转必选”的类型推导,能让我们写代码时更安全——不用再手动判断props.title == null
。
举个🌰:
// 情况1:没设置默认值 const props = defineProps<{ title?: string }>() // TS提示:props.title可能是undefined,所以title.length会报错 console.log(props.title.length) // 报错:对象可能为"undefined" // 情况2:设置默认值后 const props = withDefaults( defineProps<{ title?: string }>(), '默认标题' } ) // TS认为props.title是string(因为有默认值兜底),所以可以安全调用length console.log(props.title.length) // 正常,不会报错
这种“可选转必选”的类型推导,能帮我们在编译阶段就避免“undefined导致的运行时错误”,这也是Vue3+TS+withDefaults的核心优势之一。
实际开发哪些场景必须用它?
不是所有项目都要强制用withDefaults,但这几种场景下,它能帮你省很多事:
组件库开发
写通用组件(比如UI库的Button、Table)时,需要给props设置合理默认值,同时用TS严格约束类型,比如Table组件的columns
(默认空数组)、bordered
(默认false)这些props,用withDefaults可以让使用者一眼看到“类型要求 + 默认行为”,API文档都省了一半。
团队协作项目
多人协作时,代码规范和可读性很重要,用withDefaults把props的类型和默认值集中管理,新人接手时,看defineProps的泛型和withDefaults的默认值,能快速理解“这个props是干啥的?不传的话默认是啥?”,比起零散的注释或文档,代码本身就是最好的说明。
处理复杂类型的props
当props是联合类型(比如type: 'primary' | 'success')、泛型类型(比如T extends string)时,withDefaults能确保默认值符合类型约束,举个例子:
const props = withDefaults( defineProps<{ type?: 'primary' | 'success' | 'danger' }>(), { type: 'primary' // 符合联合类型,TS不报错 } )
如果默认值写成'danger123'(不存在的类型),TS会直接爆红,提前拦截错误。
兼容TS和运行时验证的场景
前面提到,withDefaults还能和defineProps的“运行时声明”结合,比如项目需要同时满足:TS类型检查 + 非TS环境下的props类型验证(比如给JS项目用的组件库),这时候可以这样写:
import { PropType } from 'vue' const props = withDefaults( defineProps({ String as PropType<string>, // 运行时声明+TS类型断言 list: Array as PropType<number[]>, isShow: Boolean }), { '默认标题', list: () => [], isShow: false } )
这种写法下,既有TS的类型推导,又有Vue运行时的props类型验证(比如父组件传title为数字,控制台会警告),适合需要兼容多场景的项目。
有没有错误用法?怎么避免?
日常开发中,这几种错误很常见,提前避坑能少踩雷:
错误1:默认值类型和声明不匹配
比如defineProps声明的是count?: number
,但withDefaults里给count设为字符串:
const props = withDefaults( defineProps<{ count?: number }>(), { count: '123' } // TS报错:类型'string'不能赋值给类型'number | undefined' )
避坑:写默认值前,先看defineProps里的类型声明,保证类型兼容,如果要改类型,先同步修改defineProps的泛型。
错误2:引用类型默认值没写函数
比如给数组默认值直接写[]:
const props = withDefaults( defineProps<{ list?: number[] }>(), { list: [] } // 所有组件实例共享同一个数组! )
避坑:引用类型默认值必须用函数返回”,写完后检查一下,数组、对象的默认值是不是() => []
、这种形式。
错误3:把withDefaults用在普通对象上
比如想给自定义对象加默认值:
const obj = { a: 1 } withDefaults(obj, { a: 2 }) // 报错:withDefaults只能处理defineProps的返回值
避坑:严格记住withDefaults的使用场景——只有defineProps声明的props才能用它加默认值,其他对象别瞎用。
错误4:忽略TS严格模式下的可选性变化
比如defineProps声明title?: string
,设置默认值后,在组件里写props.title.length
,因为TS认为title现在是string(不是undefined),所以不会报错;但如果没设置默认值,直接写props.title.length
,TS会提示“可能为undefined”。
避坑:理解withDefaults对TS类型的影响——设置默认值后,可选props会变成“必选且有值”,如果后续要改默认值,得同步考虑组件内对props的使用逻辑(比如原本依赖undefined的分支逻辑,可能要调整)。
用withDefaults对项目维护有啥好处?
很多同学觉得“不就是个设置默认值的API吗?不用也能写”,但长期维护下来,区别很明显:
可读性暴增:类型和默认值“绑定”
看这段代码,不用翻文档也能懂:
const props = withDefaults( defineProps<{ // 标题:可选,字符串类型: string // 列表数据:可选,数字数组 list?: number[] // 是否显示:可选,布尔值 isShow?: boolean }>(), { '默认标题', // 不传title时显示这个 list: () => [], // 不传list时给空数组 isShow: false // 不传isShow时默认隐藏 } )
所有props的“类型要求”和“默认行为”集中在一个区块,新人接手时,扫一眼就知道每个props的作用,不用在文件里到处找注释或说明。
可维护性提升:修改默认值不“拆东补西”
如果产品经理说“把title默认值改成‘新默认标题’”,直接在withDefaults里改一行就行;如果是Vue2风格的写法,可能要在props选项里找对应的default配置,更重要的是,TS会帮你检查“新默认值的类型是否符合声明”,避免改默认值时不小心改了类型(比如把string改成number)。
减少沟通成本:团队协作更丝滑
团队里约定“所有带TS的组件,props默认值用withDefaults + defineProps泛型”,
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。