简体   繁体   English

根据子类中的 static 成员类型确定 class 实例成员类型

[英]Determine class instance member type based on static member type in subclass

I have a base class called Parent :我有一个名为Parent的基础 class :

class Parent {
    static elType = window.Element
    el: InstanceType<typeof Parent['elType']>

    constructor(input: Element) {
        let ctor = this.constructor as typeof Parent
        if (input instanceof ctor.elType) {
            this.el = input
        } else {
            throw new Error()
        }
    }
}

It allows instances to be created only if input is an instance of elType specified in the constructor.只有当input是构造函数中指定的elType实例时,它才允许创建实例。 If the check passes, an instance member el is set to input .如果检查通过,则将实例成员el设置为input

Then, I want to create a subclass that allows only HTMLElement (which extends Element ) inputs:然后,我想创建一个只允许HTMLElement (扩展Element )输入的子类:

class Child extends Parent {
    static elType = window.HTMLElement
}

However, the instance member el is not correctly set to HTMLElement .但是,实例成员el未正确设置为HTMLElement It's still Element :它仍然是Element

let foo = null as unknown as HTMLElement
let ch = new Child(foo)

// Property 'offsetLeft' does not exist on type 'Element'.
ch.el.offsetLeft

I think the problem lies in this:我认为问题在于:

el: InstanceType<typeof Parent['elType']>

I'm setting the type of el to the elType type of Parent , which is Element and is not affected by Child 's static elType .我将el的类型设置为ParentelType类型,它是Element并且不受Child的 static elType的影响。 My question is - how can I make that work?我的问题是 - 我怎样才能使它工作? I need some trick like:我需要一些技巧,例如:

el: InstanceType<typeof {{ current class }}['elType']>

Check this in the playground .操场上检查这个。


I know I can solve it by explicitly declaring el in Child :我知道我可以通过在Child中明确声明el来解决它:

class Child extends Parent {
    static elType = window.HTMLElement
    el: HTMLElement
}

But I want to avoid that as it's redundant.但我想避免这种情况,因为它是多余的。 el should always be the instance type of static elType . el应该始终是static elType的实例类型。

If you weren't using static properties, I'd suggest using polymorphic this types to represent the constraint that a subclass property will narrow in concert with some other property.如果您没有使用 static 属性,我建议使用多态this类型来表示子类属性将与其他一些属性一起缩小的约束。 Something like this:像这样的东西:

class Parent {
    elType = window.Element
    el: InstanceType<this['elType']>
    constructor(input: Element) {
        if (input instanceof this.elType) {
            this.el = input as InstanceType<this['elType']>; // assert
        } else {
            throw new Error()
        }
    }
}
class Child extends Parent {
    elType = window.HTMLElement
}
let foo = null as unknown as HTMLElement
let ch = new Child(foo)
ch.el.offsetLeft; // okay

Here the type of el is declared as InstanceType<this['elType']> , which will always be related to the specific type of elType in each subclass.这里el的类型被声明为InstanceType<this['elType']> ,它总是与每个子类中elType的具体类型相关。 That makes assigning something to this.el a little tricky, since the compiler can't easily verify that such an assignment is safe for all subclasses.这使得给this.el赋值有点棘手,因为编译器不能轻易地验证这样的赋值对于所有子类都是安全的。 A type assertion is the most straightforward way around that.类型断言是最直接的方法。

Anyway you can see that this behaves almost exactly as you want, except that the elType property is an instance property and not static.无论如何,您可以看到它的行为几乎完全符合您的要求,除了elType属性是实例属性而不是 static。


If you really want to see this statically, I'd probably end up moving away from straight inheritance and instead I'd use a factory function that creates classes for you.如果你真的想静态地看到这个,我可能最终会离开直接的 inheritance 而是使用工厂 function 为你创建类。 Like this:像这样:

const ParentMaker = <T extends Element>(elType: new () => T) => {
    return class Parent {
        static elType = elType;
        el: T;
        constructor(input: Element) {
            let ctor = this.constructor as typeof Parent
            if (input instanceof ctor.elType) {
                this.el = input
            } else {
                throw new Error()
            }
        }
    }
}

const Child = ParentMaker(window.HTMLElement);
let foo = null as unknown as HTMLElement
let ch = new Child(foo)
ch.el.offsetLeft

Here ParentMaker takes a constructor as an argument and returns a new class whose static and instance side have elType and el properties strongly typed the way you want.这里ParentMaker将构造函数作为参数并返回一个新的 class ,其 static 和实例端具有elTypeel属性以您想要的方式强类型化。 Of course there's no easy route to inheritance here, but maybe that's all you need: you could always do class Child extends ParentMaker(window.HTMLElement) {... } to make Child have its own properties and methods.当然,这里没有通往 inheritance 的简单路线,但也许这就是你所需要的:你总是可以做class Child extends ParentMaker(window.HTMLElement) {... }以使Child拥有自己的属性和方法。 You'd only run into trouble if you needed sub-subclasses or for ch instanceof Parent to work.如果您需要子类或ch instanceof Parent工作,您只会遇到麻烦。


Hopefully one of those gives you some ideas for how to proceed.希望其中一个能给你一些关于如何进行的想法。 Good luck!祝你好运!

Link to code 链接到代码

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM