从另一个只读数组创建一个只读数组

Creating a readonly array from another readonly array

我有一个这样的数组:

const arr = [{id: 1, color: "blue"}, {id: 4, color: "red"}] as const

我想从中创建一个类型,如下所示:

type Colors = ["blue", "red"]

我试过这样的事情:

type ArrTransform<T extends Readonly<{id: number, color: string}[]>> = {
  [K in Exclude<keyof T, (symbol | string)>]: T[K]["color"]
}

但它似乎永远无法正确获取元素的顺序或输出数组类型的长度。

这可以用 Typescript 完成吗?

Mapped types on tuples are also tuples 自动。但是这种支持有很多“问题”,看起来您可能已经陷入了其中的几个问题。


Tuple-to-tuple 映射仅适用于 over a generic 类型。所以 type ArrTransform<T> = {[I in keyof T]: ...} 将正常工作,因为 in keyof T 使变换成为 同态 ,并且 T 是一个泛型类型参数。

一旦将其更改为 {[I in Exclude<keyof T, symbol | string>]: ...},大概是为了尝试过滤数字索引,您就使映射不再同态,事情就崩溃了。更糟糕的是,对于像 "0""1""2"tuple types, the relevant "numeric" indices are actually string literal types,而不是 number 文字类型;所以你的过滤器会丢弃你关心的索引。

因此我们将其保留为 {[I in keyof T]: ...}


如果 {[I in keyof T]: ...} 将元组变成元组,而不必尝试在左侧显式地弄乱 keyof T,那肯定意味着 I 只会被观察到迭代numeric-like 指数 T,对吧?所以 T[I] 肯定可以分配给 { id: number, color: string },对吧?错了。

令人沮丧的是,编译器似乎在映射类型的主体内部认为 I 可能是 "push""pop""length",因此甚至虽然 T 被限制为 { id: number, color: string},但 T[I] 最终可能会变成各种各样的东西,其中大部分是函数类型。有关详细信息,请参阅 microsoft/TypeScript#27995

所以我们不能只写这个:

type SadArrTransform<T extends Readonly<{ id: number, color: string }[]>> = {
  [I in keyof T]: T[I]['color'] // error
}

因为编译器假定可能 T[I] 不会有已知的 "color" 属性。相反,您需要写一些意思是“if T[I] 有一个 "color" 属性,然后是那个 属性 的类型,否则... 哦,我不在乎... never" 怎么样?看起来像这样:

type ArrTransform<T extends Readonly<{ id: number, color: string }[]>> = {
  [I in keyof T]: T[I] extends { color: infer V } ? V : never
}

这个版本现在可以按照你想要的方式运行:

type X = ArrTransform<typeof arr>;
// type X = readonly ["blue", "red"]

Playground link to code