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