◆
Vue vue-types 类型验证与组合验证器用法
基础类型只能表达「这是个数组」「这是个对象」,但实际组件的 props 经常要求更细:数组里必须都是字符串、对象必须有某几个键、某个值只能从固定几项里选。这类约束用原生 validator 手写既繁琐又不带类型推导,vue-types 提供了一组专门的验证器来处理。
这篇覆盖 oneOf(枚举取值)、oneOfType(联合类型)、arrayOf(数组项类型)、objectOf(对象值类型)、shape(对象结构),以及它们相互嵌套组合的写法。
理解这组 API 有一个关键前提:泛型标注和验证器是两套机制。array<string>() 只在编译期检查,运行时不校验数组内容;要运行时也拦住,必须用 arrayOf(String)。官方文档明确说明了这一点,代码注释里也标出来了。同理 object<User>() 只管类型推导,shape() 才做运行时的结构校验。
示例
按验证器分组看这段代码:
oneOf(['action','thriller'] as const)——限定取值范围。as const不能省,否则数组被推断成string[],泛型收窄失效、.def('test')这种错误值就拦不住了。oneOfType([String, integer(), array()])——满足其中任一类型即可。两种写法等价:显式传泛型oneOfType<string | User>([String, Object]),或者用object<User>()让类型跟着验证器走。arrayOfvsarray<T>()——前者运行时逐项校验,后者只有编译期约束。注释里那两条被注掉的.def(['my','1',2])分别演示了两者对错误值的反应差异。- 验证器可组合,这是设计上最有用的一点:
arrayOf(oneOfType([String, Number]))表示「字符串或数字的数组」,再往里套arrayOf(oneOfType([array<string>(), object<User>()]))表示「元素可以是字符串数组或 User 对象的数组」。 objectOf(String)约束的是对象所有值的类型,键不限。shape系列约束对象结构,内部字段还能各自带isRequired。默认是严格匹配——传了 shape 里没声明的键会被拒绝;末尾加.loose标记后改为宽松匹配,允许多余字段通过。注释里逐条标了哪些对象会被 accept、哪些会 reject,对照着看最直观。shape的默认值必须用工厂函数.def(() => ({...})),和 Vue 原生 props 对象类型默认值的要求一致,直接传对象字面量会让所有实例共享同一份引用。
<template>
<div>genre:{{ genre }}</div>
</template>
<script lang="ts" setup>
import {
array,
arrayOf,
integer,
object,
objectOf,
oneOf,
oneOfType,
shape,
} from 'vue-types';
type User = { name: string; id: string };
defineProps({
/** 必须是 oneOf 中提供的值之一。 */
oneOf: oneOf(['action', 'thriller'] as const).def('action'),
/** TS2345: Argument of type '"test"' is not assignable to parameter of type '"action" | "thriller" | undefined'. */
// oneOfOne: oneOf(['action', 'thriller'] as const).def('test'),
/** 必须是 oneOfType 中的类型之一 */
oneOfTypeEmpty: oneOfType([String, integer(), array()]).def(3),
/** TS2345: Argument of type 'null' is not assignable to parameter of type 'string | DefaultFactory | undefined'. */
oneOfTypeOne: oneOfType<string | User>([String, Object]),
/** 和上面的效果一致 */
oneOfTypeOnes: oneOfType([String, object<User>()]),
oneOfTypeTwo: oneOfType<string | User>([String, Object]).def({
name: 'string',
id: 'string',
}),
// oneOfTypeThree: oneOfType<string|User>([String, Object]).def({name: 'string', id: 'string', age: '11'}),
/**
* 官网给出的解释是如此
* array<string> 签名将仅在编译时验证 prop。对于运行时验证使用 arrayOf
*/
arrayOfTest: arrayOf(String).def(['my', '1']),
// arrayOfTestOne: arrayOf(String).def(['my', '1', 2]),
arrayOfTests: array<string>().def(['my', '1']),
// arrayOfTestsOne: array<string>().def(['my', '1', 2]),
// accepts an array of objects
arrayOfObj: arrayOf(object()).def([{ a: 'b' }]),
/** 验证器是可组合的。例如,要验证可以包含字符串和数字的数组,您可以使用 arrayOf 和 oneOfType: */
arrayOfType: arrayOf(oneOfType([String, Number])).def([1, '2', 'str']),
// 一个包含字符串数组和用户对象实例的数组
arrayOfUptOne: arrayOf(oneOfType([array<string>(), object<User>()])).def([
['1212'],
]),
/** TS2322: Type 'number' is not assignable to type 'string'. */
// arrayOfUptTwo: arrayOf(oneOfType([array<string>(), object<User>()])).def([[1]]),
arrayOfUptThree: arrayOf(oneOfType([array<string>(), object<User>()])).def([
{
name: '1',
id: '2',
},
]),
objectOfOne: objectOf(String).def({ name: 'John', surname: 'Doe' }),
/** TS2322: Type 'number' is not assignable to type 'string'. */
// objectOfTwo: objectOf(String).def({name: 'John', age: 30}),
// default value = {name: 'John'}
//accepts: {name: 'John', age: 30, id: 1}
//rejects: {name: 'John', age: 30} -> missing required `id` key
//rejects: {name: 'John', age: 'wrong data', id: 1} -> age is not a number
userDataOne: shape({
name: String,
age: integer(),
id: integer().isRequired,
}).def(() => ({ name: 'John' })),
userDataTwo: shape<User>({
name: String,
age: integer(),
id: integer().isRequired,
}).def(() => ({ name: 'John' })),
//accepts: {name: 'John', id: 1}
//rejects: {name: 'John', id: 1, age: 30} --> age not defined in the shape
userDataThree: shape({
name: String,
id: integer().isRequired,
}),
//accepts: {name: 'John', id: 1}
//accepts: {name: 'John', id: 1, age: 30} --> loose matching
userDataLoose: shape({
name: String,
id: integer().isRequired,
}).loose, // <-- loose flag
});
</script>
<style scoped></style>

还没有留言,来发表第一条吧~