我在Typescript中有一个Vue项目,我遇到了一个关于元组对象到联合类型对象的映射问题.

在上下文中,我正在研究后端端点的预期响应类型.当前,此端点接收2个值:枚举和字符串.响应对象将根据枚举进行更改.

这是当前的实施:

const validations = {
  email: ['isValid', 'isAvaliable'],
  password: ['isMinLengthValid', 'hasUppercase', 'hasLowercase', 'hasSpecialChars', 'hasNumbers'],
  iban: ['isValid', 'isRegistered']
} as const

type ValidationsMap = {
  [T in keyof typeof validations]: typeof validations[T][number]
}

function validate<T extends keyof ValidationsMap>(type: T, value: string): Record<ValidationsMap[T], boolean> {
  // Do something
}

现在,后端端点将再接收一个参数,响应对象也将依赖于它.

这是我try 过的,但不起作用:

const validations = {
  portal: {
    email: ['isValid', 'isAvaliable'],
    password: ['isMinLengthValid', 'hasUppercase', 'hasLowercase', 'hasSpecialChars', 'hasNumbers'],
  },
  payment: {
    email: ['isValid'],
    iban: ['isValid', 'isRegistered']
  }
} as const

type ValidationsMap = {
  [S in keyof typeof validations]: {
    [T in keyof typeof validations[S]]: typeof validations[S][T][number] // Error: Type 'number' cannot be used to index type...
  }
}

function validate<S extends keyof ValidationsMap, T extends keyof ValidationsMap[S]>(service: S, type: T, value: string): Record<ValidationsMap[S][T], boolean> {
  // Do something
}

有人知道为什么这样不行吗?

我认为可能会限制将元组映射到联合的深度,但事实似乎并非如此.

这可以正常工作:

type PortalEmailValidation = typeof validations['portal']['email'][number]

推荐答案

这似乎是TypeScript中的一个bug,如microsoft/TypeScript#27709中所述.当关键帧为generic时,类型判断器显然无法正确跟踪deep index access类型的约束.这个问题已经存在了很长时间,没有任何进展的迹象,所以现在我们所能做的就是解决它.


当编译器不接受表单T[K]的索引访问时,一种方法是使用conditional type inference,如T extends Record<K, infer V> ? V : never(使用Record<K, V> utility type).如果KT的(非可选)键,那么T将被视为VRecord<K, V>,我们可以推断.

所以我们可以写typeof validations[S][T] extends Record<number, infer V> ? V : never而不是typeof validations[S][T][number].或等效:

type ValidationsMap = {
  [S in keyof typeof validations]: {
    [T in keyof typeof validations[S]]:
    typeof validations[S][T] extends { [k: number]: infer V } ? V : never
  }
}

另一种方法是显式地添加回缺少的约束.如果您有一个类型Ayou知道它可以分配给另一个类型B,但编译器不知道这一点,那么您可以将A替换为Extract<A, B>(使用the Extract<T, U> utility type),编译器将接受它.它知道Extract<A, B>可分配给B.假设A可分配给B是正确的,那么Extract<A, B>将计算为A.

因此,如果ValidationsMap[S][T]可分配给string,但编译器看不到它,我们可以改为写Extract<ValidationsMap[S][T], string>:

function validate<S extends keyof ValidationsMap, T extends keyof ValidationsMap[S]>(
  service: S, type: T, value: string
): Record<Extract<ValidationsMap[S][T], string>, boolean> { // okay
  return null!
}

Playground link to code

Javascript相关问答推荐

是什么原因导致此Angular 16应用程序中类型错误时属性结果不存在?

防止用户在selectizeInput中取消 Select 选项

类型自定义lazy Promise. all

如何在Obsidian dataview中创建进度条

查找最长的子序列-无法重置数组

变量的值在Reaction组件中的Try-Catch语句之外丢失

从包含数百行的表中获取更改后的值(以表单形式发送到后端)的正确方法是什么?

使用POST请求时,Req.Body为空

我可以使用空手道用户界面来获取网页的当前滚动位置吗?

覆盖TypeScrip中的Lit-Element子类的属性类型

对网格项目进行垂直排序不起作用

如何创建返回不带`new`关键字的实例的类

为什么我的自定义元素没有被垃圾回收?

如何在Press上重新启动EXPO-AV视频?

FileReader()不能处理Firefox和GiB文件

JWT Cookie安全性

为什么我看到的是回复,而不是我的文档?

在此div中添加一个类,并在一段时间后在Java脚本中将其删除

此上下文在JavaScript中

如何导入我在Web Worker创建的函数?