【问题标题】:Typescript generics - one of subclasses claimed to not have inherited properties?打字稿泛型 - 声称没有继承属性的子类之一?
【发布时间】:2021-12-22 05:54:55
【问题描述】:

今天我遇到了一个我已经无法解决几个小时的错误,即使是在一个小型个人项目中也是如此。 我试图实现一个通用存储库的东西,一个非常基本的东西。 这里有几个类: 基础实体类:

import {EntityType, Fields} from "./decorators";
import {IEntity} from "./validation-interfaces";
import {applyProperties} from "./validation-core";

@EntityType()
export class Entity implements IEntity {
    //used only by db impl
    public _id:any;
    @Fields.String(true)
    key: string;
    @Fields.String(true)
    name: string;
    @Fields.String()
    organizationId?:string;
    @Fields.String()
    ownerKey?:string;
    @Fields.DateTime(true)
    createdAt: Date;
    @Fields.String(true)
    createdBy:string;
    @Fields.DateTime(false, null)
    lastUpdatedAt: Date;
    @Fields.String(false, null)
    lastUpdatedBy: string;
    @Fields.Boolean(false, false)
    recycled: boolean;
    @Fields.DateTime(false, null)
    lastRecycledAt?:Date;
    @Fields.String(false, null)
    lastRecycledBy?:string;

    @Fields.DateTime(false, null)
    lastRestoredAt?:Date;
    @Fields.String(false, null)
    lastRestoredBy?:string;

    constructor(props:Partial<Entity>) {
        this._id = props._id;
        applyProperties(Entity, props, this);
    }

}

一个组织类:

@EntityType()
export class Organization extends Entity {
    @Fields.String(true)
    companyName = "";
    @Fields.String(true)
    companyRegistrationCountryCode = "";
    @Fields.String(true)
    companyGovId = "";
    @Fields.String()
    websiteUrl = "";
    @Fields.Boolean(false, false)
    billingAddressInDifferentCountry = false;
    @Fields.Boolean(false, false)
    useDifferentEmailForInvoices = false;
    @Fields.Boolean(false, false)
    useDifferentContactPersonForInvoices = false;
    @Fields.ObjectOf(AddressDetails, true, new AddressDetails({}))
    billingAddress = new AddressDetails({});

    constructor(props:Partial<Organization>) {
        super(props);
        applyProperties(Organization, props, this);
    }
}

电子邮件消息类:

import {EntityType, Fields} from "../validation/decorators";

let {Entity, applyProperties} = require('../validation');

@EntityType()
export class EmailMessageParameters {
    [name:string]:any;
    constructor(values:any) {
        Object.assign(this, values);
    }
}

@EntityType("email-messages", 'eml')
export class EmailMessage extends Entity {
    @Fields.String(true)
    templateId: string;
    @Fields.ObjectOf(EmailMessageParameters, true, new EmailMessageParameters({}))
    parameters:any;
    @Fields.String(true)
    email:string;
    @Fields.String(true)
    displayName: string;
    @Fields.String(true)
    provider: string;
    @Fields.String(false, null)
    deliveryId: string;
    @Fields.Boolean(false, false)
    sent: boolean;
    @Fields.Boolean(false, false)
    failed:boolean;
    @Fields.String(false, null)
    errorMessage:string;
    constructor(props:Partial<EmailMessage>) {
        super(props);
        applyProperties(EmailMessage, props, this);
    }
}

以及回购工厂功能:

let reposMap = new Map<string, any>();

export type EntityConstructor<T extends Entity> = { new (props:Partial<T>):T};

export function getRepository<T extends Entity>(ctr: EntityConstructor<T>):Repository<T> {
    let validator = getValidatorForConstructor(ctr, true);
    let rep = reposMap.get(validator.id) as Repository<T>;
    if (!rep) {
        rep = new Repository<T>(ctr, validator.storageName, validator.idPrefix, validator.idStrength || 32);
        reposMap.set(validator.id, rep);
    }
    return rep;
}

行之有效:

let organizations = getRepository(Organization);

还有让我发疯的台词:

let emailMessages = getRepository(EmailMessage);

因为一个荒谬的错误:

TS2345: Argument of type 'typeof EmailMessage' is not assignable to parameter of type 'EntityConstructor<Entity>'.
Types of construct signatures are incompatible.<br/>Type 'new (props: Partial<EmailMessage>) => EmailMessage' is not assignable to type 'new (props: Partial<Entity>) => Entity'.
Type 'EmailMessage' is missing the following properties from type 'Entity': _id, key, name, createdAt, and 4 more.

像这样的显式变体

let messagesRepository = getRepository<EmailMessage>(EmailMessage);

导致其他错误:

TS2344: Type 'EmailMessage' does not satisfy the constraint 'Entity'.
Type 'EmailMessage' is missing the following properties from type 'Entity': _id, key, name, createdAt, and 4 more.

还记得 EmailMessage 是 Entity 的子类吗?

通过清除缓存重新启动 Webstorm 没有帮助。

打字稿设置位于/src/tsconfig.json,仅影响src中的内容,浏览器的内容在其他文件夹static中进行webpack-ed,其中src存在自己的ts-loader/src/tsconfig.json的内容如下:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "moduleResolution": "Node",
    "module": "commonjs",
    "target": "ES2020",
    "lib": ["ES2020"],
    "sourceMap": true,
    "outDir": "../build"
  },
  "include": ["./**/*.*"],
  "exclude": [
    "../node_modules"
  ],
  "compileOnSave": true
}

上面提到的装饰器是纯粹的,它们不覆盖构造函数,不定义属性描述符等。只是一些元数据聚合用于一些代码,如存储库实现和表单生成。

感谢您抽出宝贵时间阅读我的长文。

【问题讨论】:

  • 请提供最低限度的可重现示例,我的意思是,真的最低限度
  • 我很着急,所以我只有一段真正的代码会出现问题。我将在几分钟内发布解决方法。

标签: typescript generics


【解决方案1】:

解决方法:

我能够获得这种方法的工作版本。 我改变了什么:

  1. Repository&lt;T&gt;getRepository&lt;T&gt;() 中删除了所有高级约束,例如extends Entity
  2. 替换了
type EntityConstructor<T extends Entity> = {new (props:Partial<T>) => T};

更简单

type ConstructorFunction<T> = {new():T}

然后它终于编译并工作了。 当然,我必须添加一些额外的检查,例如是否缺少有关表和键格式的验证器详细信息,在尝试为其创建存储库时抛出错误。 这并不像仅当 T 扩展 Entity 类时才允许调用 getRepository&lt;T&gt;(ctr:ConstructorFunction&lt;T&gt;):Repository&lt;T&gt; 那样花哨,但实际上,可能会忘记在相关的 EntityType 调用中指定表名,因此无论如何保护更好。

顺便说一句,我仍然能够将参数发送到构造函数(即 Partial 值)。

【讨论】:

    猜你喜欢
    • 2021-07-26
    • 2019-03-12
    • 2022-01-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    • 2020-05-07
    • 1970-01-01
    • 2022-01-11
    相关资源
    最近更新 更多