使用泛型,如何从捕获的数组创建对象元组?

Using generics, how can I create a tuple of objects from a captured array?

给出这样的函数:

function makeObjects<T extends string[]>(...values: T);

将 return 值设置为:

T.map(v => ({ [v]: any }));

我正在使用数组映射来显示它在我脑海中的样子,是的,我知道这不是 TS 的工作方式。

您可以使用映射类型 RecordT:

中的值创建类型
function makeObject<T extends string[]>(...values: T): Record<T[number], any>{
    return null!
}

let x = makeObject("a", "b", "c")

x.a
x.d // err

Playground Link

我会给它以下调用签名:

declare function makeObjects<T extends string[]>(...values: T):
  { [I in keyof T]: { [P in Extract<T[I], string>]: any } };

(请注意,我还没有实现 makeObjects(),并认为这超出了问题的范围)。


return 类型 {[I in keyof T]: ...}mapped type over the keys of the generic input type parameter T. When T is an array or tuple type, the output type will also be an array or tuple type

对于输入数组类型 T 中的每个 numeric-like 索引 I,我们希望使用元素类型 T[I] 作为键类型。为此,您需要使用另一个映射类型,概念上类似于 {[P in T[I]]: any},这意味着“一个对象类型,其键为 T[I],其值为 any”。您也可以使用 the Record<K, V> utility type.

将其表示为 Record<T[I], any>

不幸的是,虽然您只关心 numeric-like 索引,但编译器认为 I 可能是 any key of T,包括像 "push""pop" 这样的数组方法名称,因此 属性 类型 T[I] 可能是您不想用作键的各种东西。 (有关此问题的讨论,请参阅 ms/TS#27995)。

解决这个问题的方法是将 T[I] 包装在编译器同意的肯定是 key-like 的内容中。由于您只关心 T[I]string(因为 T extends string[]),我们可以使用 the Extract<T, U> utility typeT[I] 过滤为 string-like 事物。

所以这给了你 { [I in keyof T]: { [P in Extract<T[I], string>]: any }}.


我们来测试一下:

const foo = makeObjects("a", "b", "c");
// const foo: [{  a: any; }, { b: any; }, { c: any; }] 

看起来不错;输出类型是对象的元组,其键来自 makeObjects().

的相应参数

Playground link to code