在Typescript中使用带有通用参数的类型作为构造函数

时间:2019-07-08 14:08:20

标签: typescript generics

考虑以下打字稿代码:

type CtorType<T> = {
  new(...args: any[]): T;
}

type IWrapper<T> = {
  value: T;
}

function foo<T>(ctor: CtorType<T>): IWrapper<T> {
  return {
    value: new ctor('foo')
  };
}

class A { }
class B<T> { }

const a = foo(A);
const b = foo(B<number>);

奇怪的是,TS编译器在最后一行抛出错误:

TS2348: Value of type 'typeof B<number>' is not callable. Did you mean to include 'new'?

在操场上,我还看到TS将foo(A)编译为foo(A),但是将foo(B<number>)编译为foo(B()),这更加令人惊讶。

为什么会这样?是否有一种解决方法可以使其使用B并维护type参数?

1 个答案:

答案 0 :(得分:1)

我正在更改您的示例代码,以停止奇怪的问题,例如类型AB<string>B<number>都相同,并且不调用构造函数A或他们不接受带有B参数的"foo"

即使在示例代码中,拥有empty classesunused type parameters也是一个坏主意,因为它们通常会导致意外的结果。

并且由于您的CTorType<T>使用了(...args: any[])参数列表,因此它忘记了对构造函数参数的任何约束,并允许进行不安全的调用。当foo()的实现最终以两个A作为参数调用B"foo"构造函数时,构造函数都不接受任何参数。因此,我将使用一个空的参数列表(),仅在new ctor()内部调用foo()

赞:

// zero arg constructor to prevent unsafe calls
type CtorType<T> = {
  new (): T; 
};

type IWrapper<T> = {
  value: T;
};

function foo<T>(ctor: CtorType<T>): IWrapper<T> {
  return {
    value: new ctor() // zero arg call
  };
}

// make A not empty
class A {
  a = "a";
}
// make B not empty and depend on T
class B<T> {
  b: T | undefined = undefined;
}

这只是为了将示例代码放到一个地方,唯一的问题就是您要解决的问题。


好的,继续:

B<number>是语法错误;编译器将其解释为具有指定类型参数的泛型函数调用的第一部分,例如在B<number>(13)中,假设Bfunction B<T>(x: T){}类似。但是B不是可调用函数,它是构造函数,因此您会收到该错误。如果可以通话,则会提示您错过了通话(即"(" expected)。有一个名为B<number>类型,但没有一个名为B<number> value (在TypeScript中为values are not generic)。

没有简短的语法可以在不调用或new的情况下在函数或构造函数中指定通用参数。您可以做的一件事是将构造函数从一个泛型(可以作用于type参数的所有值)扩展到一个具体的(只能作用于type参数的单个值)。 ,就像这样:

const BNumber: new () => B<number> = B;
const b = foo(BNumber); // IWrapper<B<number>>

第一个BNumber起作用是因为B实际上是new () => B<number>(以及new () => B<string>new () => B<Array<{a: string}>>等)。然后,当您在foo上调用BNumber时,它将根据需要返回一个IWrapper<B<number>>

如果您经常使用B做这种事情,则可以将其扩展为您要调用的函数,如下所示:

const BWidener = <T>(): new () => B<T> => B;
const bAlso = foo(BWidener<number>()); // IWrapper<B<number>>
const bString = foo(BWidener<string>()); // IWrapper<B<string>>
const bWhatever = foo(BWidener<Array<{ a: string }>>()); // IWrapper<B<{a: string;}[]>>

或者,如果这只是您要做的一次,并且不介意使用type assertions,那么您可以在一行中这样做:

const bAsWell = foo(B as new () => B<number>); // IWrapper<B<number>>

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

Link to code