[英]Getting error: Type 'typeof B' is not assignable to type 'typeof A' for class B that extends A
Reproducible example here此处可重现的示例
My need is: the contentType
parameter should accept any class object extended from Content (PublicContent, AdminContent, PrivateContent, etc) and I want to call a static method from this parameter type inside the execute
method.我的需要是:
contentType
参数应该接受从 Content(PublicContent、AdminContent、PrivateContent 等)扩展的任何 class object 并且我想在此参数类型的execute
方法中调用 static 方法
I have a method with the following signature:我有一个具有以下签名的方法:
async execute<U extends ContentProps>(input: {
contentType: typeof Content;
contentPropsType: typeof ContentProps;
}): Promise<Result<U, Failure>>;
and a class hierarchy as follows:和 class 层次结构如下:
// 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
}
The issue is that when I call the execute
method passing PublicContent
as the contentType
parameter I'm getting an error saying问题是,当我调用传递
PublicContent
作为contentType
参数的execute
方法时,我收到一条错误消息
Type 'typeof PublicContent' is not assignable to type 'typeof Content'
类型“typeof PublicContent”不可分配给类型“typeof Content”
The method call is:方法调用是:
const result = await this.getContent.execute({
contentType: PublicContent,
contentPropsType: PublicContentProps,
});
My question is: Why I'm getting this error since PublicContent
is extending Content
?我的问题是:为什么我会收到此错误,因为
PublicContent
正在扩展Content
?
EDIT: as requested by @Chase, the full types for Entity
and EntityProps
:编辑:根据@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;
}
}
The problem you're running into is that superclass/subclass constructors do not always form a type hierarchy even if their instances do.您遇到的问题是超类/子类构造函数并不总是形成类型层次结构,即使它们的实例确实如此。 Let's look at an example:
让我们看一个例子:
class Foo {
x = 1;
constructor() { }
static z = 3;
}
class Bar extends Foo {
y: string;
constructor(y: number) {
super()
this.y = y.toFixed(1);
}
}
Here, class Bar extends Foo
means if you have a value of type Bar
, you can assign it to a variable of type Foo
:在这里,
class Bar extends Foo
意味着如果你有一个Bar
类型的值,你可以将它分配给一个Foo
类型的变量:
const bar: Bar = new Bar(2);
const foo: Foo = bar; // okay
But if you try to assign the Bar
constructor (of type typeof Bar
) to a value of the same type as the Foo
constructor (of type typeof Foo
), it fails:但是,如果您尝试将
Bar
构造函数( typeof Bar
类型)分配给与Foo
构造函数( typeof Foo
类型)相同类型的值,则会失败:
const fooCtor: typeof Foo = Bar; // error!
// Type 'new (y: number) => Bar' is not assignable to type 'new () => Foo'
That's because the Bar
constructor requires a parameter of type number
when you call its construct signature (ie, new Bar(2)
), while the Foo
constructor takes no parameters at all (ie, new Foo()
).这是因为
Bar
构造函数在调用其构造签名时需要一个number
类型的参数(即new Bar(2)
),而Foo
构造函数根本不接受任何参数(即new Foo()
)。 If you try to use Bar
as if it were the Foo
constructor, and call it with no parameters, you'll get a runtime error:如果您尝试像使用
Foo
构造函数一样使用Bar
,并且不带参数调用它,您将收到运行时错误:
const oopsAtRuntime = new fooCtor(); // TypeError: y is undefined
For the same reason, your PublicContent
constructor is not assignable to typeof Content
.出于同样的原因,您的
PublicContent
构造函数不能分配给typeof Content
。 The former requires a construct signature parameter of type PublicContentProps
, while the latter will accept any parameter of type that extends ContentProps
.前者需要一个
PublicContentProps
类型的构造签名参数,而后者将接受任何扩展ContentProps
类型的参数。 If you try to use PublicContent
as if it were the Content
constructor, you might pass it a parameter of some type other than PublicContentProps
, and that could lead to errors.如果您尝试将
PublicContent
用作Content
构造函数,您可能会向它传递PublicContentProps
以外的某种类型的参数,这可能会导致错误。
So let's step back.所以让我们退后一步。 In fact you don't care if the object you pass as
contentType
is assignable to the Content
constructor type because you're not going to call its construct signature with an arbitrary ContentProps
.实际上,您并不关心您作为
contentType
传递的 object 是否可分配给Content
构造函数类型,因为您不会使用任意ContentProps
调用其构造签名。 You really only care about the type of its static create()
method.你真的只关心它的 static
create()
方法的类型。 I'd be inclined to write getContent()
as a generic function like this:我倾向于将
getContent()
写为通用的 function,如下所示:
const getContent = <U extends ContentProps, T extends BaseEntity<U>>(input: {
contentType: Pick<typeof Content, "create"> & (new (entityProps: U) => T);
contentPropsType: new () => U;
}): U => { /* impl */ };
That should work similarly to your existing version on the inside of the function, and now you can call it without error, because PublicContent
matches the create
method of Content
, as well as being a constructor of type (new (entityProps: PublicContentProps) => PublicContent)
:这应该与 function 内部的现有版本类似,现在您可以毫无错误地调用它,因为
PublicContent
匹配Content
的create
方法,并且是类型(new (entityProps: PublicContentProps) => PublicContent)
:
const entity = getContent({
contentType: PublicContent,
contentPropsType: PublicContentProps,
}); // okay
Try to make the Content
implements a simple interface, like IContent
, and use this interface to get the typeof
on execute
parameter尝试让
Content
实现一个简单的接口,比如IContent
,并使用这个接口来获取execute
参数的typeof
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.