@AntowaKartowa

Как типизировать класс с динамическими именами методов создаваемых в конструкторе?

Занимаюсь постепенным переписыванием legacy кода на проекте с JavaScript на TypeScript и дошло дело до API класса. На данный момент варианта переписать его с нуля не рассматриваю так как опасаюсь завязнуть.

Класс объявляет методы (размещаемые в прототипе) _readMany, _create, _read, _update, _delete. В конструкторе на основе строкового параметра type создаются динамические имена новых свойств экземпляра и им присваиваются ссылки на методы класса. Например, значением type является 'User', то для него будут сгенерированы имена свойств 'getUsers', 'createUser', 'getUser', 'updateUser', 'deleteUser'. Опущу несколько методов в коде для простоты.

class API {
  _url;

  constructor(type, url) {
    this._url = url;

    this[`get${type}s`] = this._readMany;
    this['create' + type] = this._create;
    this['get' + type] = this._read;
  }

  _readMany(params = {}) {
    const options = getApiOptions(Method.GET);
    const url = new URL(this._url);
    url.search = new URLSearchParams(params);
    
    return fetch(url.toString(), options);
  }

  _create(body) {
    const options = getApiOptions(Method.POST, body);
    
    return fetch(this._url, options);
  }

  _read(id) {
    const options = getApiOptions(Method.GET);
    
    return fetch(`${this._url}/{id}`, options);
  }
}


Как можно и возможно ли вообще типизировать такие динамические методы? Спасибо.

PS. Во время редактирования случайно нажал Tab и Space, после чего вопрос опубликовался раньше времени и я не успел отредактировать сложность вопроса. А после публикации её, видимо, нельзя изменить.
  • Вопрос задан
  • 43 просмотра
Решения вопроса 1
Aetae
@Aetae Куратор тега TypeScript
Тлен
TypeScript очень плохо относится к динамическим членам класса. По сути в самой декларации класса ты ничего не сможешь с этим поделать, и тебе придётся руками кастовать то что надо.

Однако в целях поддержки лекаси ты можешь просто подменить тип, что-то вроде:
type MethodNames<T extends string> = `get${T}s` | `create${T}` | `get${T}`;

interface IAPI {
  new <T extends string>(type: T, url: string): {
    [K in MethodNames<T>]: Function
  }
}

export default API as unknown as IAPI;
сработает как надо. Естественно ты можешь доработать интерфейс IAPI до полного совпадения.

При декларации самого класса ты максимум можешь добавить неспецифичную index signuture, условно:
type MethodNames<T extends string> = `get${T}s` | `create${T}` | `get${T}`;
class API {
  [key: MethodNames<string>]: Function

  _url;

  constructor(type: string, url: string) {
    this._url = url;

    this[`get${type}s`] = this._readMany;
    this[`create${type}`] = this._create;
    this[`get${type}`] = this._read;
  }

  _readMany(params = {}) {
    const options = getApiOptions(Method.GET);
    const url = new URL(this._url);
    url.search = new URLSearchParams(params);
    
    return fetch(url.toString(), options);
  }

  _create(body) {
    const options = getApiOptions(Method.POST, body);
    
    return fetch(this._url, options);
  }

  _read(id) {
    const options = getApiOptions(Method.GET);
    
    return fetch(`${this._url}/{id}`, options);
  }
}

Но это уже будет не так удобно и чётко.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы