我想键入一个函数,该函数以数组元素或由 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
的类型?
答案 0 :(得分:2)
引用此线程-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>
Playground
函数 a
中参数对象中属性 fn
的推断类型为 ArgType<A | B>[]
。如果您检查 X
的类型,它是 Class<A | B> | [Class<A | B>, never]
。也就是说,键的类型为 never
,因为 keyof A
和 keyof B
的交集是 never
。
目前在 Typescript 中似乎不可能做这样的事情。
其他链接-
答案 1 :(得分:2)
首先,确保编译器不会改变你的数组,换句话说,是一个元组。如果传入数组字面量,则必须使用 const 断言 (as const
)。一旦你这样做,你会看到你定义的类型不适合这个任务:
类型 'readonly [typeof A, readonly [typeof A, "a"], typeof B, readonly [typeof B, "b"]]' 是 'readonly' 并且不能分配给可变类型 '(Class | [Class, never])[]'
请注意,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
类型包装成映射类型以纠正缺点。
应该这样做(我们所做的就是借助 keyof
utility type 将元组的第二个成员重新约束为 InstanceType
):
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)[]