我想键入一个函数,该函数以数组元素或数组数组的形式将各种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 A
和keyof B
的交集never
。
到目前为止,似乎不可能在 Typescript 中做这样的事情。
其他链接-
- TypeScript 泛型只会在简单情况下推断联合类型
- https://github.com/microsoft/TypeScript/issues/31617#issuecomment-496867013
首先,确保编译器你的数组不会变异,换句话说,是一个元组。如果传入数组文字,则必须使用 const 断言 (as const
)。执行此操作后,您将看到定义的类型不适合该任务:
readonly [typeof B, "b"]]' 是"只读",不能分配给可变类型"(类 |[类,从不])[]'
请注意,keyof T
解析为never
,因为keyof unknown
never
。为什么?因为签名接受可变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)[]
<小时 />游乐场