一种方法是在实例类型的tuple type个T
中创建foo()
个generic.所以如果你调用foo([[A, B], [C, D]])
,那么泛型类型参数T
就是[A, C]
.x
的类型将是mapped type over that tuple,其中T
的每个元素将被转换为具有您所关心的约束的一对类构造函数类型.
从概念上讲,这看起来像这样:
function foo<T extends object[]>(x: { [I in keyof T]: [
abstract new (...args: any) => T[I],
new (...args: any) => T[I]
}) { }
对于T
个元组的每个数字索引I
,T[I]
实例类型在x
类型的对应第I
个元素中使用两次.它是该对的第一个元素中的实例类型(可能是abstract
),也是该对的第二个元素中的类构造函数的实例类型.
请注意,这并不完全强制第二个构造函数扩展第一个构造函数,只是它们都是构造和实例的同一类型.但如果我们确保T[I]
只是从这对元素中的第一个元素而不是第二个元素推断出来的,这将自然而然地发生(好吧,对于"扩展"的某些定义而言).所以我们想说,第二个T[I]
只存在于类型checking,而不是类型inference.有一个长期的功能要求,即用大约NoInfer<T>
个操作员在microsoft/TypeScript#14829的位置上标记non-inferential type parameter usage.这方面没有本机操作符,但GitHub问题描述了一些实现它的方法,这些方法适用于某些用例.在这里,我将在这一期中写下其中一个建议:
type NoInfer<T> = [T][T extends any ? 0 : never];
现在我们有了
function foo<T extends object[]>(x: { [I in keyof T]: [
abstract new (...args: any) => T[I],
new (...args: any) => NoInfer<T>[I]
}) { }
我们已经很接近了.现在唯一的问题是,编译器可能不想将T
推断为元组,因为数组文字往往被推断为无序数组而不是元组.为了向编译器提示我们更喜欢元组,我们可以将x
类型包装在variadic tuple type([...
+]
)中:
function foo<T extends object[]>(x: [...{ [I in keyof T]: [
abstract new (...args: any) => T[I],
new (...args: any) => NoInfer<T>[I]]
}]) { }
好的,现在让我们测试一下:
foo([[A, B], [C, D]]); // okay
// function foo<[A, C]>(x: ⋯): void
根据需要推断出正确的调用.不正确的调用应该会产生错误:
foo([[A, D]]) // error
// ----> ~
// Type 'typeof D' is not assignable to type 'new (...args: any) => A'.
// function foo<[A]>(x: ⋯): void
在这里,编译器将T
推断为[A]
,由于D
不是A
兼容对象的构造函数,因此会出现警告.您还会在此处收到警告:
foo([[A, B], [D, C]]) // error
// ------------> ~
// Type 'typeof C' is not assignable to type 'new (...args: any) => D'.
// Cannot assign an abstract constructor type to a non-abstract constructor type.
// function foo<[A, D]>(x: ⋯): void
但这仅仅是因为foo()
不允许每对元素中的第二个元素是abstract
.您对C
和D
类的定义并没有将它们彼此区分开来.典型的例子:
class B2 { foo() { } }
foo([[A, B2]]) // this is okay
// function foo<[A]>(x: ⋯): void
这不是错误,因为B2
的实例等于structurally compatible加上A
,即使B2
的声明中没有extends
子句.TypeScrip的类型系统主要是 struct 化的,因此它不会识别或关心B
和B2
之间的任何差异.如果您需要真正关心的行为,您将不得不反对类型系统和/或使用各种技巧,例如包含private
or protected
class members.这就是为什么我之前说过,这取决于您对"扩展"的定义,这是否足够.希望does就足够了,但如果不够,关于 struct 类型系统中nominal types的近似值的讨论可能就不在这里了.
Playground link to code个