◆

Vue vue-types 类型验证与组合验证器用法

2023-02-02 01:370

基础类型只能表达「这是个数组」「这是个对象」,但实际组件的 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>() 让类型跟着验证器走。
  • arrayOf vs array<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>
💬留言板0

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

PAGE LOAD / LIVEVue vue-types 类型验证与组合验证器用法加载中