【问题标题】:Determine class instance member type based on static member type in subclass根据子类中的静态成员类型确定类实例成员类型
【发布时间】:2019-10-23 02:13:58
【问题描述】:

我有一个名为 Parent 的基类:

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()
        }
    }
}

仅当input 是构造函数中指定的elType 的实例时,它才允许创建实例。如果检查通过,则将实例成员 el 设置为 input

然后,我想创建一个只允许HTMLElement(扩展Element)输入的子类:

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

但是,实例成员 el 未正确设置为 HTMLElement。还是Element:

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

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

我认为问题在于:

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

我将el的类型设置为ParentelType类型,即Element,不受Child的静态elType的影响。我的问题是 - 我怎样才能使它工作?我需要一些技巧,例如:

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

the playground 中查看。


我知道我可以通过在Child 中明确声明el 来解决它:

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

但我想避免这种情况,因为它是多余的。 el 应该始终是 static elType 的实例类型。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    如果您不使用静态属性,我建议使用polymorphic this types 来表示子类属性将与其他属性一起缩小的约束。像这样的:

    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
    

    这里el的类型被声明为InstanceType&lt;this['elType']&gt;,这将始终与每个子类中elType的具体类型相关。这使得给this.el 赋值有点棘手,因为编译器不能轻易地验证这样的赋值对于所有子类都是安全的。类型断言是最直接的方法。

    无论如何,您可以看到它的行为几乎完全符合您的要求,除了 elType 属性是实例属性而不是静态属性。


    如果您真的想静态地看到这一点,我可能最终会放弃直接继承,而是使用为您创建类的工厂函数。像这样:

    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
    

    这里ParentMaker 将构造函数作为参数并返回一个新类,其静态和实例端具有elTypeel 属性以您想要的方式强类型化。当然,这里没有简单的继承途径,但也许这就是你所需要的:你总是可以使用class Child extends ParentMaker(window.HTMLElement) { ... } 来使Child 拥有自己的属性和方法。只有当您需要子类或 ch instanceof Parent 才能工作时,您才会遇到麻烦。


    希望其中一个能给你一些关于如何进行的想法。祝你好运!

    Link to code

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-24
      • 2019-07-13
      • 1970-01-01
      • 2018-10-05
      • 1970-01-01
      • 2023-03-26
      相关资源
      最近更新 更多