Как исправить «Тип« BaseCtor »нельзя назначить типу« T »при использовании обобщений с параметрами по умолчанию» - PullRequest
2 голосов
/ 03 июня 2019

Я пытаюсь создать экземпляр, используя 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 голосов
/ 03 июня 2019

Возможно, вы уже знаете это, но, возможно, стоит пройти через это:

Параметр типа универсальной функции разрешается вызывающей стороной, а не разработчиком. Кроме того, вызывающим абонентам разрешается указывать это значение вручную, используя явный тип в угловых скобках, если переданные параметры соответствуют этому. Таким образом, все следующие способы являются допустимыми для вызова 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 ?

Вас удивляет * 1007? В этом случае вызывающая сторона вручную указала T, равную Foo, но передала только один параметр. Компилятор будет использовать значение конструктора по умолчанию BaseCtor, но BaseCtor не создает Foo экземпляров! Вот в чем заключается ошибка, которую вы получаете. Это говорит о том, что BaseCtor нельзя гарантировать присвоение T, поскольку все, что мы знаем, это то, что T расширяет BaseCtor.


Очевидно, что вам нужен какой-то способ, чтобы вызывающий не смог указать какой-либо подтип BaseCtor при использовании только одного аргумента. Вероятно, самый простой инструмент для достижения этой цели - перегрузка функции . Предоставьте две разные сигнатуры вызовов и одну реализацию, например:

// 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

Хорошо, надеюсь, это поможет. Удачи!

Ссылка на код

...