Определите параметры типа с помощью статических членов

Я пытаюсь создать несколько служебных классов для своего веб-сервиса RESTful, чтобы уменьшить количество шаблонного кода.

Вот упрощенный пример моих служебных классов:

class Model {
    public static async findAll(options: any) {
        return Promise.resolve([]);
    }
}

class Service<M> {
    public model: M;
    // this will fix the following problem with "findAll". But we can't access "User" model members in "UsersService"
    // public model: typeof Model;

    constructor(options: { model: any }) {
        this.model = options.model;
    }

    public async find(params?: any): Promise<any> {
        return this.model.findAll(params); // error: Property 'findAll' does not exist on type 'M'
    }
}

Пример использования:

class User extends Model {
    firstName: string;
    lastName: string;

    constructor(options: any) {
        super();

        this.firstName = options.firstName;
        this.lastName = options.lastName;
    }
}

class UsersService extends Service<typeof User> {
    constructor() {
        super({ model: User });
    }

    public async customMethod() {
        const users = this.find({});

        const newUser = new this.model({ firstName: 'foo', lastName: 'bar' });

        newUser.firstName // ok
        newUser.lastName // ok
    }
}

Я не могу понять, как мне определить параметры типа для класса Service, чтобы я мог получить доступ к findAll в классе Service, а также получить доступ к полям firstName и lastName в UsersService?

Если я определяю параметр типа как M extends Model, я все равно не могу получить доступ к статическим членам.

Пример REPL


person Denis Volok    schedule 04.08.2020    source источник
comment
Ближайшее, что я могу найти, - это class Service<M extends typeof Model> {, что заставляет все работать, за исключением того, что я получаю Type 'typeof User' does not satisfy the constraint 'typeof Model'., что не имеет для меня особого смысла.   -  person Alex Wayne    schedule 04.08.2020
comment
Я не уверен, но конструкторы обычно не являются частью «типа» класса. Было бы полезно определить интерфейс, который определяет функцию new(), чтобы все, реализующее этот интерфейс, было вынуждено реализовать сигнатуру конструктора совместимым образом.   -  person Evert    schedule 05.08.2020
comment
@Evert actualy Model - это библиотечная функция, поэтому дублировать эти объявления может быть немного беспорядочно.   -  person Denis Volok    schedule 05.08.2020


Ответы (1)


Ты можешь сделать это:

class Model {
    public static async findAll(options: any) {
        return Promise.resolve([]);
    }

    public f(): string { return ''}
}

type AnyConstructor = new (...args: unknown[]) => unknown;
type StaticProperties<T extends AnyConstructor> = Pick<T, keyof T>;

class Service<M extends StaticProperties<typeof Model>> {
    public model: M;

    constructor(options: { model: M }) {
        this.model = options.model;
    }

    public async find(params?: any): Promise<any> {
        return this.model.findAll(params); // error: Property 'findAll' does not exist on type 'M'
    }
}

class User extends Model {
    firstName: string;
    lastName: string;

    constructor(options: any) {
        super();

        this.firstName = options.firstName;
        this.lastName = options.lastName;
    }
}

class UsersService extends Service<typeof User> {
    constructor() {
        super({ model: User });
    }

    public async customMethod() {
        const users = this.find({});

        const newUser = new this.model({ firstName: 'foo', lastName: 'bar' });

        newUser.firstName // ok
        newUser.lastName // ok
    }
}

На данный момент создание интерфейса для статических свойств - это своего рода беспорядок. Надеюсь, это предложение может упростить задачу.

person Karol Majewski    schedule 04.08.2020
comment
Да, это решение решает проблему с ошибкой, но тогда мне нужно написать повторяющийся код (модель является библиотечным классом и содержит множество статических методов, которые можно использовать). Подумал, есть более удобное решение. Вероятно, мне следует проверить использование модели в моем коде. - person Denis Volok; 05.08.2020
comment
Понятно. Чтобы удалить дублирование, вы можете создать помощник, который извлекает статические свойства из конструкторов. Таким образом, вы могли просто читать их из Model, не переопределяя их. Я обновил свой ответ примером реализации. - person Karol Majewski; 05.08.2020