我在Typescript中有一个Vue项目,我遇到了一个关于将元组对象映射到Union类型对象的问题。
对于上下文,我正在处理后端端点的预期响应类型。目前,这个端点接收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
}
现在后端端点将接收更多的参数,并且响应对象也将依赖于它。
这是我尝试过的,但它不工作:
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
}
有人知道为什么这不起作用吗?
我认为它允许将Tuple映射到Union的深度可能有限制,但事实似乎并非如此。
可以正常运行:
type PortalEmailValidation = typeof validations['portal']['email'][number]
这似乎是TypeScript中的一个错误,如microsoft/TypeScript#27709所述。当键是泛型时,类型检查器显然不能正确地跟踪深度索引访问类型的约束。这个问题已经存在很长时间了,没有任何进展的迹象,所以现在我们所能做的就是解决它。
当编译器不接受形式为T[K]
的索引访问时,一种方法是使用条件类型推断,就像T extends Record<K, infer V> ? V : never
一样(使用Record<K, V>
实用程序类型)。如果K
是T
的(非可选)键,那么T
将被视为某些V
的Record<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
}
}
另一种方法是显式地添加回缺失的约束。如果你有一个类型A
,你知道可以分配给另一个类型B
,但编译器不知道这一点,那么你可以用Extract<A, B>
替换A
(使用Extract<T, U>
实用程序类型),编译器将接受它。它知道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链接到代码