我正在为我的TypeScript
库开发一项功能,并且遇到了这种打字问题。请看下面的例子:
class Base { ... }
class User extends Base { ... }
class Product extends Base { ... }
class CompletelyUnrelated { ... }
function someFunction<TModel>(source: TModel, base: {what_to_type_here}) { ... };
// Desired result
someFunction(User, Base); // works
someFunction(Product, Base); // works
someFunction(CompletelyUnrelated, Base); // error
someFunction(User, Product); // error
someFunction(Product, User); // error
someFunction(User, CompletelyUnrelated); // error
为了更多地解释所需的输出,我想让someFunction()
期望User
和Base
作为其参数,因为User extends Base
。 Product
和Base
相同。有(甚至)一种方法可以实现这一目标吗?有什么我想要实现的目标吗?
我尝试过:
interface Constructible<TModel> { // an interface to annotate a new-able type (arbitrary, put here for context)
new(...args: any[]): TModel;
}
type BaseConstructible<TModel> = TModel extends infer TBase ? Constructible<TBase> : any;
但是,那没有用,结果我总是得到Constructible<TModel>
:
type Test = BaseConstructible<User>; // returns Constructible<User>
任何帮助/指导将不胜感激。谢谢
更新:正如@jcalz所指出的,TypeScript
具有结构类型系统,我也意识到这一点。 @jcalz的答案是完全真实和正确的。但是,这并不是我想要的(毕竟在TypeScript中根本不可能)。这是一个例子:
function anotherFunction<TBase , TModel extends TBase>(source: Constructible<TModel>) {
return (base: Constructible<TBase>) => {
// function body
}
}
anotherFunction(User)(Product); // no error
TypeScript
一无所知,或者根本无法从TBase
中推断出TModel extends TBase
。我想知道是否有一种方法可以让TypeScript
来推断TBase
。
答案 0 :(得分:2)
首先,TypeScript的类型系统为structural, not nominal;粗略地讲,这意味着如果两个类型具有相同的 shape ,则即使它们具有不同的 declaration ,它们也是相同的 type 。这与Java,C ++或C#中的标称类型形成鲜明对比,其中class A {}
和class B {}
是不同的类型,只是因为它们具有不同的声明。
例如,如果您在TypeScript中的示例类为empty,则它们将被视为与空接口{}
相同,并且彼此相同。因此,可能是您所看到的某些奇怪行为,例如,CompletelyUnrelated
实际上被认为是 same 类型,您的其他课程之一,例如Base
。如果您希望编译器将两个类视为不同的类,那么拥有不同的类声明是不够的。您需要两个类具有不同的成员(或者它们需要具有private
或protected
成员,这将提供更多的nominal-like behavior)。
这里有些类具有足够的属性,因此Base
,User
,Product
和CompletelyUnrelated
完全按照您想要的方式关联:
class Base {
base = "base"
}
class User extends Base {
user = "user"
}
class Product extends Base {
product = "product"
}
class CompletelyUnrelated {
completelyUnrelated = "whatevz";
}
现在,我们可以为someFunction()
编写类型签名了。我将使用两种通用类型:T
,代表base
构造函数的实例类型;和U
,代表source
构造函数的实例类型。我们希望constrain U
成为extends T
,我们希望source
和base
参数成为构造函数:
function someFunction<T, U extends T>(
source: new (...args: any) => U,
base: new (...args: any) => T
) { return null! };
让我们看看它是否有效:
someFunction(User, Base); // okay
someFunction(Product, Base); // okay
someFunction(CompletelyUnrelated, Base); // error
// -------> ~~~~~~~~~~~~~~~~~~~
// Property 'base' is missing in type 'CompletelyUnrelated' but required in type 'Base'
someFunction(User, Product); // error
// --------> ~~~~
// Property 'product' is missing in type 'User' but required in type 'Product'.
someFunction(Product, User); // error
// --------> ~~~~~~~
// Property 'user' is missing in type 'Product' but required in type 'User'.
someFunction(User, CompletelyUnrelated); // error
// --------> ~~~~
// 'completelyUnrelated' is missing in type 'User' but required in type 'CompletelyUnrelated'
看起来不错。 someFunction()
成功和失败都在您想要的位置。好吧,希望能有所帮助;祝你好运!
更新:
如果您想将someFunction()
分解为一个名为anotherFunction()
的{{3}}版本,那么您将需要做一些更多的类型操作来实现这一点。主要问题是编译器希望anotherFunction(SomeCtor)
的返回值是一个确定的类型,因此您需要将该类型表示为仅允许类实例类型的 supertype 的类型。对应于SomeCtor
。
从概念上讲,您希望类型看起来像<U>(source: new (...args: any)=>U) => <T super U>(base: new (...args: any)=>T) => void
,其中T super U
是表示下限 curried而不是的假设语法T extends U
表示的上限。因此T super U
和U extends T
的含义应该相同。
不幸的是,TypeScript中没有T super U
下界约束。 generic constraint用于相关功能请求。您可以使用条件类型来获得此行为。您可以写T super U
来代替T extends ([U] extends [T] ? unknown : never)>
:这将最终执行U extends T
检查([U] extends [T]
通过U
中的并集关闭See microsoft/TypeScript#14520)如果成功则变为T extends unknown
,这始终为真;如果失败,则变为T extends never
,其本质上始终为假。因此,像这样:
function anotherFunction<U>(source: new (...args: any) => U) {
return <T extends ([U] extends [T] ? unknown : never)>(base: new (...args: any) => T) => {
}
}
让我们看看它是否有效:
anotherFunction(User)(Base); // okay
anotherFunction(Product)(Base); // okay
anotherFunction(Base)(Product); // error!
// -----------------> ~~~~~~~
// Type 'Product' is not assignable to type 'never'
anotherFunction(CompletelyUnrelated)(Base); // error!
// --------------------------------> ~~~~
// Type 'Base' is not assignable to type 'never'
anotherFunction(User)(Product); // error!
anotherFunction(Product)(User); // error!
anotherFunction(User)(CompletelyUnrelated); // error!
这正是您想要的,除了错误可能很奇怪:“ Base
不可分配给never
”,而正确的是,对“ CompletelyUnrelated
的帮助并没有帮助不能分配给Base
“。因此,这里仍然有一些限制。
好的,希望能再有帮助。祝你好运。