打字稿|键入动态课程

时间:2020-02-05 09:06:44

标签: typescript types

我有一个包含多个子类的类,这几乎是一个魔术类,并且执行一些逻辑来帮助我与json api集成。

在这种情况下,该类称为Resource,该类的工作是使其能够像Laravel模型一样使用它。因此,我扩展了该类,显然可以从该类中使用这些方法,但是在此子类上,我就有了变异器和其他逻辑,以这个示例为例。

class Resource {
    constructor(attributes: Attributes) {
        const keys: string[] = Object.keys(attributes);

        keys.forEach((attributeKey: string) => {
            this.setAttribute(attributeKey, attributes[attributeKey]);

            Object.defineProperty(this, attributeKey, {
                get: () => this.getAttribute(attributeKey),
                set: (value: any) => this.setAttribute(attributeKey, value);
            });
        });
    }

    ...
}
class User extends Resource {
    setFirstNameAttribute(value: string): void {
        this.attributes.firstName = value.toUpperCase();
    }
}

这会将类上的firstName属性转换为全部大写,这由Resource类的构造函数中定义的getter和setter完成。

一切正常,例如...

const user = new User({ firstName: 'John', lastName: 'Doe' });
console.log(user.firstName);

实际上会输出JOHN,因此父类内部的所有逻辑都可以正常工作。

我唯一遇到的问题是类型,因为属性是动态设置的,我似乎无法找到一种获取此类型的方法,因为我希望对类中的方法进行类型提示,所以{ {1}}不会出错,但是user.find()也不会出错,但是因为user.firstName不是实际属性,所以我无法找到提示它的方法。

我尝试设置类似firstName的内容,但不确定我是否完全理解这一点。

1 个答案:

答案 0 :(得分:0)

您可以添加通用类型参数来表示属性的类型。天真的,您认为您可以将此通用类型参数添加到类的extends或implement子句(即class ResourceBase<T> extends T)中,但是由于多种原因,这是不可能的。

您可以做的是定义基类,而不能动态访问键(即user.firstName不起作用)。然后,您可以将此基础转换为泛型构造函数,该构造函数返回基类和类型参数(new <T>(attr: T) => ResourceBase<T> & T)的交集。在继承该构造函数的情况下,要给出具体的类型(即不是类型参数),一切都会正常进行。

注意:在getAttribute中也使setAttributeK extends keyof T通用,以完全捕获每个属性的键和键类型。

class ResourceBase<T> {
    constructor(protected attributes: T) {
        const keys = Object.keys(attributes) as Array<keyof T>;

        keys.forEach((attributeKey) => {
            this.setAttribute(attributeKey, attributes[attributeKey]);

            Object.defineProperty(this, attributeKey, {
                get: () => this.getAttribute(attributeKey),
                set: (value: any) => this.setAttribute(attributeKey, value);
            });
        });
    }
    getAttribute<K extends keyof T>(key: K) : T[K] {
        return this.attributes[key];
    }
    setAttribute<K extends keyof T>(key: K, value: T[K]) {
        this.attributes[key] = value;
    }
}

const Resource: new <T>(attr: T) => ResourceBase<T> & T = ResourceBase as any


class User extends Resource<{
    firstName: string,
    lastName: string
}> {
    setFirstNameAttribute(value: string): void {
        this.attributes.firstName = value.toUpperCase();
    }
}


const user = new User({ firstName: 'John', lastName: 'Doe' });
console.log(user.firstName);

Playground Link