Ant*_*y S 6 typescript typescript-generics typescript-typings
我有某种元组类型的一个值,我想将其映射到不同类型的不同元组。元组允许是异构的。有没有一种方法可以在不使用下面示例中的类型转换的情况下执行此映射?
interface SomeType<T> {
value: T
}
type SomeTypeList<T extends any[]> = { [K in keyof T]: SomeType<T[K]> }
interface OtherType<T> {
otherValue: T
}
type OtherTypeList<T extends any[]> = { [K in keyof T]: OtherType<T[K]> }
function convertValues<T extends any[]> (arr: SomeTypeList<T>): OtherTypeList<T> {
// Is there any way to write this without the cast?
return arr.map(({ value }) => ({ otherValue: value })) as OtherTypeList<T>
}
convertValues<[number, string, boolean]>([{ value: 1 }, { value: 'cat' }, { value: true }])
// => [{ otherValue: 1 }, { otherValue: 'cat' }, { otherValue: true }]
Run Code Online (Sandbox Code Playgroud)
通过强制转换,它会失去类型安全性,因为回调可能会返回({ otherValue: 1 }),并且所有内容都会正确进行类型检查。
不,从 TS4.1 开始,这在 TypeScript 中是不可能的。我看到有两个问题;一个可以通过更改 TypeScript 标准库类型来克服Array.prototype.map(),但另一个需要对类型系统进行相当大的更改才能正常工作,并且目前无法以某种意义上不等效的方式进行处理到类型断言(您所说的“强制类型转换”)。
数组方法的当前库类型是:map()
interface Array<T> {
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
}
Run Code Online (Sandbox Code Playgroud)
返回类型U[]是与参数的输出类型相对应的无序数组类型callbackfn。它不是一个元组。有一个开放的 GitHub 问题microsoft/TypeScript#29841要求更改此设置,以便当您调用map()元组时,您会得到相同长度的元组。目前尚未实施;但您可以使用声明合并来自己测试这样的更改:
// declare global { // uncomment if in a module
interface Array<T> {
map<This extends Array<T>, U>(this: This, fn: (v: T) => U): { [K in keyof This]: U }
}
interface ReadonlyArray<T> {
map<This extends ReadonlyArray<T>, U>(
this: This, fn: (v: T) => U): { [K in keyof This]: U }
}
// }
Run Code Online (Sandbox Code Playgroud)
如果我调用(将参数convertValues()更改为可变元组类型,这会给编译器一个提示,如果可能的话,它应该解释为元组),您可以看到它现在如何知道返回值中有多少个元素:arrarr
function convertValues<T extends any[]>(arr: [...SomeTypeList<T>]) {
return arr.map(({ value }) => ({ otherValue: value }))
}
const ret = convertValues([{ value: 1 }, { value: 'cat' }, { value: true }])
ret[0]; ret[1]; ret[2]; // okay
ret[3] // error! Tuple type of length '3' has no element at index '3'.
Run Code Online (Sandbox Code Playgroud)
不幸的是,每个元组元素的类型未知:
ret[0].otherValue.toFixed(2); // error!
// -------------> ~~~~~~~
// Property 'toFixed' does not exist on type 'string | number | boolean'.
Run Code Online (Sandbox Code Playgroud)
每个元素只有编译器知道{otherValue: string | number | boolean}。这并没有错,但也不是您想要的。
因此,让我们退一步思考一下,您需要怎样map()才能实现您想要的功能。当您调用 时arr.map(),您会将回调视为一个泛型函数,它将泛型类型的输入转换SomeType<V>为类型 的输出OtherType<V>(对于任何 )V。否则,编译器就没有机会注意到输入元组的每个元素与输出元组的相应元素之间的相关性。map()您确实可以在调用时通过注释回调来编写:
function convertValues<T extends any[]>(arr: [...SomeTypeList<T>]) {
return arr.map(<V,>(
{ value }: SomeType<V>
): OtherType<V> => ({ otherValue: value }))
}
Run Code Online (Sandbox Code Playgroud)
问题是......你如何描述这种可能做其他事情的通用回调?我想您不想对map()类型进行硬编码以关心专门SomeType<V>变成OtherType<V>. 你想说“回调变成F<X>任何G<X>和F” G:
// not valid TypeScript, don't use it
interface Array<T> {
map<A extends any[], F<?>, G<?>>(
this: { [K in keyof A]: F<A[K]>},
fn: <V>(v: F<V>) => G<V>
): { [K in keyof A]: G<A[K]> }
}
Run Code Online (Sandbox Code Playgroud)
但在 TypeScript 中无法表达这一点。F和G以上不是泛型类型,而是泛型类型函数或类型构造函数。而这些在 TypeScript 中是不存在的。泛型类型构造函数需要引入所谓的高级类型,这可以在一些函数式编程较多的语言(如 Haskell 和 Scala)中找到。有一个长期开放的功能请求,microsoft/TypeScript#1213要求这样做,但谁知道它是否会被实现。这将是一项相当大的工作,所以我没有屏住呼吸(但很想看到它!)。并且有一些可能的方法可以在 TypeScript 中模拟更高种类的类型(您可以阅读 GitHub 问题以了解更多信息),但我不想为您的用例推荐任何内容。
所以我们被困住了。目前无法编写 的map()类型来让编译器验证 的convertValues()实现是否符合您声明返回的类型。
当编译器无法验证某物的类型是否是您声明的类型,并且您确信您的声明仍然正确时,您非常需要执行类似类型断言之类的操作,就像您已经完成的那样。因此,我建议您继续按照此处所示的方式进行操作,并重新审视 TypeScript 中是否出现了更高种类的类型。
| 归档时间: |
|
| 查看次数: |
1446 次 |
| 最近记录: |