动态、基于变量或索引的泛型类型变量



我想键入一个函数,该函数以数组元素或数组数组的形式将各种classes作为输入,该数组由class及其method名称组成。像这样:

fn([Class1, Class2, [Class3, 'method3'], ..., ClassN]);

我还想严格指出,如果选择了[[class, method]]模式,则可以仅指定此特定类的方法。

type Class<T> = new (...args: any[]) => T;
class A {
a(): any {}
}
class B {
b(): any {}
}
// ...
function fn<T>(arg: Array<
Class<T> // <-- pick type of T dynamically??
| [Class<T>, keyof T] // <-- pick type of T dynamically??
>): any {
// do something with arg..
}
fn([
A,        // ok
[A, 'a'], // ok
B,        // type error: Property 'a' is missing in type 'B' but required in type 'A'.
[B, 'b'], // same type error..., expecting only "b" available here
]);

类型变量T有没有办法在每次迭代(数组元素)上选择class的类型?

操场

引用此线程 - https://github.com/microsoft/TypeScript/issues/31617 和以下代码-

type Class<T> = new (...args: any[]) => T;
class A {
a(): any {}
}
class B {
b(): any {}
}
type ArgType<T> = Class<T> | [Class<T>, keyof T]
type ArgDefinition<T> = {
[K in keyof T]: ArgType<T[K]>[]
}
declare function fn<T>(arg: ArgDefinition<T>): void
fn({a: [
A,
[A, 'a'], // shows error
B,
[B, 'b'], // shows error
]})
type X = ArgType<A | B>

操场 函数fn参数中的对象中的属性a的推断类型为ArgType<A | B>[]。如果选中X的类型,则为Class<A | B> | [Class<A | B>, never]。也就是说,键的类型为never,因为keyof Akeyof B的交集never

到目前为止,似乎不可能在 Typescript 中做这样的事情。

其他链接-

  1. TypeScript 泛型只会在简单情况下推断联合类型
  2. https://github.com/microsoft/TypeScript/issues/31617#issuecomment-496867013

首先,确保编译器你的数组不会变异,换句话说,是一个元组。如果传入数组文字,则必须使用 const 断言 (as const)。执行此操作后,您将看到定义的类型不适合该任务:

类型"只读 [typeof A, readonly [typeof A, "a"], typeof B,

readonly [typeof B, "b"]]' 是"只读",不能分配给可变类型"(类 |[类,从不])[]'

请注意,keyof T解析为never,因为keyof unknownnever。为什么?因为签名接受可变Array,而我们传递ReadonlyArray。这很容易解决,但将我们带到原点,因为T被推断为元组第一个元素的类型:

function fn<T>(arg: ReadonlyArray<
Class<T> // <-- pick type of T dynamically??
| [Class<T>, keyof T] // <-- pick type of T dynamically??
>): any {
// do something with arg..
}
fn([A] as const); //function fn<A>(arg: readonly (Class<A> | [Class<A>, "a"])[]): any

因此,其次,我们必须阻止编译器推断T。如何?最简单的方法(假设您不关心传入的构造函数的确切类型)是停止传递实例类型并满足于非泛型Ctor

type Ctor = new (...args: any[]) => any;
function fn2<T extends readonly (Ctor | readonly [Ctor, string])[]>(arg: T): any {
// do something with arg..
}
fn2([
A,        // ok
[A, 'a'], // ok
B,        // ok
[B, 'b'], // ok
] as const)

现在一切都很好,但是我们失去了将元组的第二个成员约束到实例keyof的能力(这就是为什么唯一的约束是string)。由于T现在被推断为具有正确键入成员的元组,因此我们可以将arg类型包装为映射类型以纠正缺点。

像这样的事情应该做(我们所做的只是在InstanceType实用程序类型的帮助下将元组的第二个成员重新约束为keyof):

type Constrained<T extends readonly (Ctor | readonly [Ctor, string])[]> = {
[P in keyof T] : T[P] extends readonly any[] ? readonly [ T[P][0],  keyof InstanceType<T[P][0]> ] : T[P]
};

瞧,生成的签名是严格输入的:

fn2([
A,        // ok
[A, 'a'], // ok
B,        // ok
[B, 'b'], // ok
[B, "missing"] // Type '"missing"' is not assignable to type 'keyof B'
] as const)
fn2([[A, "a"]]) //ok
fn2([[A, "unknown"]]) //Type '"unknown"' is not assignable to type '"a"'

请注意,我们甚至可以删除as const断言,但要小心:如果这样做,传入的元组必须是同类的,否则元组的第二个成员将不会缩小为字符串文字:

fn2([[A, "never"], B]) //ok as the inferred type is ([typeof A, string] | typeof B)[]
<小时 />

游乐场

最新更新