打字稿:将元组映射到联合类型似乎在嵌套对象中不起作用



我在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>实用程序类型)。如果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
}
}

另一种方法是显式地添加回缺失的约束。如果你有一个类型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链接到代码

最新更新