打字稿:自引用静态类成员

时间:2021-01-28 17:28:11

标签: typescript typescript-generics

静态方法中的 github issue 上有一个 Polymorphic this,还有一个相关的问题 here。这些正在讨论如何在静态类方法的情况下解决/解决问题。但是,在静态类成员的情况下,我找不到任何解决此问题的参考。 我们有一个模型类,这个类有一些包含映射的静态成员,其中键是模型字段。我们在继承的类中使用属​​性装饰器来标记模型字段(继承的类只包含字段定义)。装饰器还将字段添加到基类中定义的静态映射。请参阅下面和 playgound

中的代码
function field(): any {
    return function (target: Base, propertyKey: ModelFields<Base>, _descriptor: PropertyDescriptor) {
        if (!target.constructor.fields) {
            target.constructor.fields = {};
        }
        target.constructor.fields[String(propertyKey)] = String(`s_${propertyKey}`);
    };
}

class Base {
    ['constructor']: typeof Base;

    static fields: Record<string, string>;
    // Actually the above is supposed to be Record<keyof ModelFields<this>, string> but 'this' is not allowed here
}

class A extends Base {
    @field()
    public propA: string = 'A';
}

class B extends Base {
    @field()
    public propB: string = 'B';
}

type ModelFields<T extends Base> = Required<Omit<T, keyof Base>>

console.log(B.fields) // B.fields type is not correct here

目前静态的 fields 被定义为 Record<string, string>,但它并没有说明上面存在哪些键,尽管我们知道有效的键是 keyof ModelFields<this>。但显然 this 是不允许出现的。

有没有办法让 fields 的输入正确?

1 个答案:

答案 0 :(得分:1)

你可以做一些事情来接近你想要的效果。

您发现的第一个问题是静态成员中缺少多态 this。我们可以用一个方法替换该字段。在一个方法上,我们可以使用泛型类型来捕获调用站点类型:

class Base {
    ['constructor']: typeof Base;

    static fields: Record<any, any>;
    static getFields<TThis extends typeof Base>(this: TThis)  {
        type I = InstanceType<TThis>;
        return this.fields as { 
            [P in keyof I] : string
        }
    }
}

Playground Link

另一个问题是没有办法基于装饰器过滤成员(这些在类型系统中没有表示)。您可以做的是将品牌添加到所有具有装饰器的字段的类型中,并过滤该品牌的字段:


class Base {
    ['constructor']: typeof Base;

    static fields: Record<any, any>;
    static getFields<TThis extends typeof Base>(this: TThis)  {
        type I = InstanceType<TThis>;
        return this.fields as { 
            [P in keyof I as I[P] extends FieldBrand<unknown> ? P : never] : string
        }
    }
}

declare const fieldBrand: unique symbol;
type FieldBrand<T> =T &  { [fieldBrand]?: never }
type UnBrandField<T> = T extends FieldBrand<infer U> ? U: never;
class A extends Base {
    @field()
    public propA: FieldBrand<string> = 'A';
}

Playground Link