根据子类中的静态成员类型确定类实例成员的类型

时间:2019-10-22 09:43:57

标签: typescript

我有一个名为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的类型设置为elType的{​​{1}}类型,这是Parent,不受Element的静态影响Child。我的问题是-我该怎么做?我需要一些技巧,例如:

elType

the playground中进行检查。


我知道我可以通过在el: InstanceType<typeof {{ current class }}['elType']> 中明确声明el来解决此问题:

Child

但是我想避免这种情况,因为它是多余的。 class Child extends Parent { static elType = window.HTMLElement el: HTMLElement } 应该始终是el的实例类型。

1 个答案:

答案 0 :(得分: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<this['elType']>,它将始终与每个子类中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