Воспроизводимый пример здесь
Мне нужно: параметр contentType
должен принимать любой объект класса, расширенный из Content (PublicContent, AdminContent, PrivateContent и т. Д.), И я хочу вызвать статический метод из этого типа параметра внутри метода execute
.
У меня есть метод со следующей подписью:
async execute<U extends ContentProps>(input: {
contentType: typeof Content;
contentPropsType: typeof ContentProps;
}): Promise<Result<U, Failure>>;
И иерархия классов следующим образом:
// content.entity.ts
export class ContentProps extends EntityProps {}
export class Content<T extends ContentProps> extends Entity<T> {
public constructor(props: T) {
super(props);
}
}
// public-content.entity.ts
export class PublicContentProps extends ContentProps {
readonly title: string;
readonly text: string;
}
export class PublicContent extends Content<PublicContentProps> {
constructor(props: PublicContentProps) {
super(props);
}
// ommited
}
Проблема в том, что когда я вызываю метод execute
, передавая PublicContent
в качестве параметра contentType
, я получаю сообщение об ошибке
Тип 'typeof PublicContent' не может быть назначен типу 'typeof Content'
Вызов метода:
const result = await this.getContent.execute({
contentType: PublicContent,
contentPropsType: PublicContentProps,
});
У меня вопрос: почему я получаю эту ошибку, поскольку PublicContent
расширяет Content
?
РЕДАКТИРОВАТЬ: по запросу @Chase, полные типы для Entity
и EntityProps
:
// entity.ts
export abstract class EntityProps extends BaseEntityProps {
id?: string;
createdAt?: Date;
updatedAt?: Date;
}
export abstract class Entity<T extends EntityProps> extends BaseEntity<T> {
get id(): string {
return this.props.id;
}
get createdAt(): Date {
return this.props.createdAt;
}
get updatedAt(): Date {
return this.props.updatedAt;
}
protected constructor(entityProps: T) {
super(entityProps);
}
}
// base.entity.ts
export abstract class BaseEntityProps {}
export abstract class BaseEntity<T extends BaseEntityProps> extends Equatable {
protected readonly props: T;
protected constructor(baseEntityProps: T) {
super();
this.props = baseEntityProps;
}
static create<T = BaseEntity<BaseEntityProps>, U = BaseEntityProps>(
this: {
new (entityProps: U): T;
},
propsType: { new (): U },
props: U,
): Result<T, ValidationFailure> {
const violations = validateSchemaSync(propsType, props);
return violations?.length
? Result.fail(new ValidationFailure(violations))
: Result.ok(new this({ ...props }));
}
toJSON(): T {
return this.props;
}
}
2 ответа
Проблема, с которой вы столкнулись, заключается в том, что конструкторы суперкласса / подкласса не всегда образуют иерархию типов, даже если их экземпляры формируют. Давайте посмотрим на пример:
class Foo {
x = 1;
constructor() { }
static z = 3;
}
class Bar extends Foo {
y: string;
constructor(y: number) {
super()
this.y = y.toFixed(1);
}
}
Здесь class Bar extends Foo
означает, что если у вас есть значение типа Bar
, вы можете присвоить его переменной типа Foo
:
const bar: Bar = new Bar(2);
const foo: Foo = bar; // okay
Но если вы попытаетесь назначить Bar
конструктор (типа typeof Bar
) значению того же типа, что и конструктор Foo
(типа {{X3 }}), это не удается:
const fooCtor: typeof Foo = Bar; // error!
// Type 'new (y: number) => Bar' is not assignable to type 'new () => Foo'
Это потому, что конструктору Bar
требуется параметр типа number
, когда вы вызываете его подпись конструкции (т. Е. new Bar(2)
), а конструктор Foo
вообще не принимает параметров (т. Е. , new Foo()
). Если вы попытаетесь использовать Bar
, как если бы это был конструктор Foo
, и вызвать его без параметров, вы получите ошибку времени выполнения:
const oopsAtRuntime = new fooCtor(); // TypeError: y is undefined
По той же причине ваш конструктор PublicContent
не может быть назначен typeof Content
. Первый требует параметра сигнатуры конструкции типа PublicContentProps
, тогда как последний принимает любой параметр типа, расширяющего ContentProps
. Если вы попытаетесь использовать PublicContent
, как если бы это был конструктор Content
, вы могли бы передать ему параметр какого-либо типа, кроме PublicContentProps
, и это могло бы привести к ошибкам.
Так что вернемся назад. На самом деле вас не волнует, может ли объект, который вы передаете как contentType
, назначить типу конструктора Content
, потому что вы не собираетесь вызывать его подпись конструкции с произвольным ContentProps
. Вам действительно важен только тип его статического метода create()
. Я был бы склонен написать getContent()
как общую функцию, подобную этой:
const getContent = <U extends ContentProps, T extends BaseEntity<U>>(input: {
contentType: Pick<typeof Content, "create"> & (new (entityProps: U) => T);
contentPropsType: new () => U;
}): U => { /* impl */ };
Это должно работать аналогично вашей существующей версии внутри функции, и теперь вы можете вызывать ее без ошибок, потому что PublicContent
соответствует методу create
для Content
, а также является конструктор типа (new (entityProps: PublicContentProps) => PublicContent)
:
const entity = getContent({
contentType: PublicContent,
contentPropsType: PublicContentProps,
}); // okay
Детская площадка ссылка на код
Попробуйте сделать так, чтобы Content
реализовал простой интерфейс, например IContent
, и используйте этот интерфейс, чтобы получить параметр typeof
on execute
Похожие вопросы
Новые вопросы
javascript
По вопросам программирования на ECMAScript (JavaScript / JS) и его различных диалектах / реализациях (кроме ActionScript). Включите все соответствующие теги в свой вопрос; например, [node.js], [jquery], [json] и т. д.