是否可以从派生类推断基类类型?

时间:2020-02-04 00:11:25

标签: typescript generics

我正在为我的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()期望UserBase作为其参数,因为User extends BaseProductBase相同。有(甚至)一种方法可以实现这一目标吗?有什么我想要实现的目标吗?

我尝试过:

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

1 个答案:

答案 0 :(得分:2)

首先,TypeScript的类型系统为structural, not nominal;粗略地讲,这意味着如果两个类型具有相同的 shape ,则即使它们具有不同的 declaration ,它们也是相同的 type 。这与Java,C ++或C#中的标称类型形成鲜明对比,其中class A {}class B {}是不同的类型,只是因为它们具有不同的声明。

例如,如果您在TypeScript中的示例类为empty,则它们将被视为与空接口{}相同,并且彼此相同。因此,可能是您所看到的某些奇怪行为,例如,CompletelyUnrelated实际上被认为是 same 类型,您的其他课程之一,例如Base。如果您希望编译器将两个类视为不同的类,那么拥有不同的类声明是不够的。您需要两个类具有不同的成员(或者它们需要具有privateprotected成员,这将提供更多的nominal-like behavior)。

这里有些类具有足够的属性,因此BaseUserProductCompletelyUnrelated完全按照您想要的方式关联:

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,我们希望sourcebase参数成为构造函数:

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()成功和失败都在您想要的位置。好吧,希望能有所帮助;祝你好运!

Playground link to code


更新:

如果您想将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 UU 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“。因此,这里仍然有一些限制。

好的,希望能再有帮助。祝你好运。

distributing