如何向TypesScript类添加动态属性并保持正确的键入

时间:2019-05-16 23:08:10

标签: typescript typescript-typings typescript2.0

我有一个Users类,我正在从文件Users.ts导出

    export default class Users {}

然后我从另一个文件Users.ts中导出index.ts

    // classes
    export {default as Users} from './Users'

我有第三个文件Foo.ts,我想在其中动态实例化index.ts中所有导出的类,并将它们作为属性添加到该类:

    import * as classes from './index'

    class Foo {
        constructor() {
           const httpClient = new HttpClient()
        }

        _addClasses() {
           for (const class in classes) {
             this[class] = new classes[class](this.httpClient);
           }
        }
    }

我的问题是,如何为Foo添加正确的类型,以便可以在IDE中为.users获得正确的自动补全功能,例如:

new Foo(new HttpClient).users

1 个答案:

答案 0 :(得分:1)

该问题的第一部分是创建一个包含导入模块实例类型的新类型。为此,我们将使用预定义的条件类型InstanceType来提取类的实例类型。要获取模块的类型,我们将使用typeof classes。将其全部包装为映射类型,我们得到:

type ClassInstances = {
    [P in keyof typeof classes]: InstanceType<typeof classes[P]>
}

// For the example above this is equivalent to 
type ClassInstances = {
    Users: classes.Users;
}

现在,我们需要将这些新属性添加到类中。为此,无需显式定义它们,我们可以使用一个空的类表达式作为Foo的基类,并断言该空类返回的实例具有这些成员(实际上不是,但是我们和这些成员_addClasses中的所有内容都可以解决)。全部放在一起,我们得到:

import * as classes from './index';

type ClassInstances = {
    [P in keyof typeof classes]: InstanceType<typeof classes[P]>
}

class Foo extends (class {} as new () => ClassInstances) {
    httpClient: HttpClient;
    constructor() {
        super();
        this.httpClient = new HttpClient()
        this._addClasses();
    }

    _addClasses() {
        for (const cls of Object.keys(classes) as Array<keyof typeof classes>) {
            this[cls] = new classes[cls](this.httpClient);
        }
    }
}

new Foo().Users // ok now name is the same as the name used in the export in index.ts so it's upper case. No string manipulation on string properties.