Как добавить динамические свойства в класс TypesScript и поддерживать правильную типизацию

У меня есть 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

person TheNastyOne    schedule 16.05.2019    source источник
comment
Я нигде не вижу определения свойства .users, за исключением того, что вы пытаетесь его использовать. И ваш конструктор Foo не принимает никаких параметров.   -  person Erik Philips    schedule 17.05.2019
comment
это правильно, я бы хотел, чтобы тип был выведен.   -  person TheNastyOne    schedule 17.05.2019
comment
Возможный дубликат Объявлять динамически добавляемые свойства класса в TypeScript   -  person Etheryte    schedule 17.05.2019
comment
@Nit, вы правы, но я искал, чтобы избежать создания функции-оболочки + принуждение типов.   -  person TheNastyOne    schedule 17.05.2019


Ответы (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.
person Titian Cernicova-Dragomir    schedule 17.05.2019