Как использовать ES6 Proxy с TypeScript?

Примеры кода постарался максимально упростить, убрав лишнее.
Если кому-то будет интересно - доклад о таком подходе к реализации сервиса который мы пытаемся подружить с TypeScript

Есть сервис для работы с API (класс, экземпляр которого создаётся при старте приложения).
При создании ему передаются настройки, в том числе список эндпоинтов:

const apiClient = createApiClient({
  endpoints: {
    doTest: { url: '/test', method: 'get'},
  },
});


С помощью Proxy получается вместо

api.doRequest(endpoints.getData)

сделать что-то вроде виртуальных методов

api.getData()

Попытки реализовать это на TypeScript приводят либо к ошибке

TS2339: Property 'getData' does not exist on type 'ApiClient'

type RequestMethod = 'get'| 'post';

type Endpoint = {
  method: RequestMethod;
  url: string;
};

type EndpointsConfig = Record<string, Endpoint>;

class ApiClient {
  endpoints: EndpointsConfig;

  constructor(endpoints: EndpointsConfig) {
    this.endpoints = endpoints;
  }

  doRequest(url: string, method: RequestMethod) : void {
    console.log(`url: ${url}`);
    console.log(`method: ${method}`);
  }
}

function createApiClient(endpoints: EndpointsConfig) {
  return new Proxy(new ApiClient(endpoints), {
    get(target: ApiClient, name: keyof ApiClient) {
      if (target.endpoints && target.endpoints[name]) {
        return () => (
          target.doRequest(target.endpoints[name].url, target.endpoints[name].method)
        );
      }

      return target[name];
    },
  });
}

const apiClient = createApiClient({
  getData: { url: '/data', method: 'get' },
});

apiClient.getData();



Либо к тому, что приходится определять эндпоинты не в конфиге, а непосредственно хардкодить в сервисе (что уже такое себе). При этом даже работает автокомплит, но

TS2722: Cannot invoke an object which is possibly 'undefined'.

import { Api } from './Api';

const endpoints = {
  getData: { url: '/data', method: 'get' },
};

interface IEndpoint {
  method: 'get'|'post';
  url: string,
}

type EndpointsConfig = {
  [K in keyof typeof endpoints]: IEndpoint;
};

type EndpointsProxy = {
  [K in keyof EndpointsConfig]?: () => unknown;
}

type IApiProxy = { [K in keyof Api]: Api[K] } & EndpointsProxy;

function createApi() : IApiProxy {
  return new Proxy(new Api(), {
    get(target: Api, name: keyof Api) {
      if (target.endpoints && target.endpoints[name]) {
        return () => (
          target.doRequest(target.endpoints[name])
        );
      }

      return target[name];
    },
  });
}

const api = createApi();

api.getData();



или

TS2322: Type 'Api' is not assignable to type 'IApiProxy'.   Property 'getData' is missing in type 'Api' but required in type 'EndpointsProxy'


если убрать необязательность методов-эндпоинтов

type EndpointsProxy = {
  [K in keyof EndpointsConfig]: () => unknown;
}


Буду благодарен за помощь. Может быть я что-то не понимаю/не вижу, а может быть в целом такой подход не применим с TS.
  • Вопрос задан
  • 1513 просмотров
Решения вопроса 1
Пригласить эксперта
Ответы на вопрос 1
profesor08
@profesor08
В ts с proxy проблемы быть не может просто потому, что он принимает сигнатуру объекта. А это значит, что надо работать с самим объектом, который попадает в proxy. Если сигнатура заранее не известна, то для этого ввели дженерики.

В данном случае надо будет расширить ApiClient так, чтоб он мог иметь дополнительные методы.
Ответ написан
Комментировать
Ваш ответ на вопрос

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

Похожие вопросы