在使用具有默认参数的泛型时,如何解决“类型'BaseCtor'不能分配给类型'T'”的问题

时间:2019-06-03 11:52:19

标签: typescript typescript-generics

我正在尝试通过将createInstance与泛型一起使用来创建实例,如下所示。当我向函数传递两个参数时,它工作得很好。如果在没有第二个参数Ctor的情况下调用该函数,则它将默认提供BaseCtor。在这种情况下,它会引发类似“类型'BaseCtor'不可分配给类型'T'”的错误。我应该怎么做才能解决所有争论?

class BaseCtor {}
const createInstance = function createInstance<T extends BaseCtor>(
  options: object = {},
  Ctor: {new (): T} = BaseCtor
): T {
  return new Ctor();
}

1 个答案:

答案 0 :(得分:0)

您可能已经知道这一点,但是值得一遍:

泛型函数的类型参数由调用者而不是实现者解析。此外,只要传递的参数符合此要求,允许调用者使用尖括号中的显式类型手动指定此值。因此,以下所有方法都是调用createInstance()的有效方法:

class Foo extends BaseCtor {
  prop = "me";
}

const i1 = createInstance({ a: "okay" }, Foo); // Foo
const i2 = createInstance<Foo>({ a: "okay" }, Foo); // Foo
const i3 = createInstance({ a: "okay" }); // BaseCtor
const i4 = createInstance<Foo>({ a: "okay" }); // Foo ?

i4会让您感到惊讶吗?在这种情况下,调用者已手动指定TFoo,但仅传入一个参数。编译器将使用默认构造函数值BaseCtor,但BaseCtor不会构造Foo个实例!这就是您所得到的错误的全部原因。就是说BaseCtor不能保证可分配给T,因为我们所知道的是T 扩展 BaseCtor


很显然,您需要某种方法,以便在仅使用一个参数时,调用方无法指定BaseCtor的某些子类型。 function overload可能是实现此目的的最简单工具。提供两个不同的呼叫签名和一个实现,如下所示:

// call signature: two args
function createInstance<T extends BaseCtor>(
  options: object,
  Ctor: { new (): T }
): T;
// call signature: zero or one arg
function createInstance(options?: object): BaseCtor;

// implementation signature
function createInstance(
  options: object = {},
  Ctor: { new (): BaseCtor } = BaseCtor
): BaseCtor {
  return new Ctor();
}

或等效地(更紧密地匹配您的代码):

const createInstance = function createInstance(
  options: object = {},
  Ctor: { new (): BaseCtor } = BaseCtor
): BaseCtor {
  return new Ctor();
} as {
  <T>(options: object, Ctor: new () => T): T;
  (options?: object): BaseCtor;
};

现在i4是不可能的,其余的呼叫将按照您希望的方式运行:

const i1 = createInstance({ a: "okay" }, Foo); // Foo
const i2 = createInstance<Foo>({ a: "okay" }, Foo); // Foo
const i3 = createInstance({ a: "okay" }); // BaseCtor
const i4 = createInstance<Foo>({ a: "okay" }); // error! expected 2 args, got 1

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

Link to code