Возврат сигнатуры функции на основе общих параметров - PullRequest
1 голос
/ 22 мая 2019

У меня есть функция createRequest:

function createRequest(method: string, path: string) {
  return function resourceApiCall() {
    // ...additional logic
    return httpCall(path, method) 
  }
}

, которая возвращает функцию resourceApiCall, которую я хотел бы вызвать как:

const fetchUsers = createRequest('GET', '/users')

await fetchUsers({createdAfter: new Date()})

Я также хотел бы сделать что-то вроде:

const fetchPayment = createRequest('GET', '/payments')

await fetchPayment('id', {createdAfter: new Date()})

У меня вопрос, как я могу передать определение createRequest, чтобы fetchUsers и fetchPayment отображали правильные параметры функции и возвращаемое значение внутри IDE (любая проверка типов правильно)?

Я считаю, что мне нужно сделать что-то вроде:

interface FetchPayment {
  (id: string, {createdAfter: Date}): Promise<{id: string}>
}

const fetchPayment = createRequest<FetchPayment>('GET', '/payments')

но в идеале я хотел бы сделать что-то вроде:

const fetchPayment = createRequest<Args, Result>('GET', '/payments')

function createRequest<Args, Result>(method: string, path: string) {
  return function resourceApiCall(...args: Args) {
    // ...additional logic
    return httpCall<Result>(path, method) 
  }
}

Ответы [ 2 ]

1 голос
/ 22 мая 2019

Вы могли бы действовать следующим образом:

// some interfaces you expect httpCall to return
interface User {
  name: string;
  age: number;
}
interface Payment {
  id: string;
}

// a mapping of request paths to the function signatures
// you expect to return from createRequest
interface Requests {
  "/users": (clause: { createdAfter: Date }) => Promise<Array<User>>;
  "/payments": (id: string, clause: { createdAfter: Date }) => Promise<Payment>;
}

// a dummy httpCall function
declare function httpCall<R>(path: string, method: string, payload: any): R;

// for now only GET is supported, and the path must be one of keyof Requests
function createRequest<P extends keyof Requests>(method: "GET", path: P) {
  return (function resourceApiCall(
    ...args: Parameters<Requests[P]> // Parameters<F> is the arg tuple of function type F
  ): ReturnType<Requests[P]> { // ReturnType<F> is the return type of function type F
    return httpCall<ReturnType<Requests[P]>>(path, method, args);
  } as any) as Requests[P]; // assertion to clean up createRequest signature
}

async function foo() {
  const fetchUsers = createRequest("GET", "/users");
  const users = await fetchUsers({ createdAfter: new Date() }); // User[]
  const fetchPayment = createRequest("GET", "/payments");
  const payment = await fetchPayment("id", { createdAfter: new Date() }); // Payment
}

В приведенном выше примере я использую интерфейс Requests, чтобы указать на уровне типа отображение от пути запроса до нужной сигнатуры функции createRequest() возвращать.И createRequest() - это универсальная функция, использующая Requests для строгого ввода возвращаемой функции.Обратите внимание, что внутри реализации resourceApiCall() я также использую некоторые встроенные условные типы для извлечения типов аргументов и возвращаемого типа из сигнатуры функции.Это не является строго обязательным, но делает печатание внутри resourceApiCall() более явным.

В любом случае, надеюсь, это поможет.Удачи!


ОБНОВЛЕНИЕ: Вот возможный способ разбить это на разные модули так, чтобы каждый модуль касался только своей конечной точки.

Сначала, получите файл с createRequest()в нем вместе с изначально пустым интерфейсом Requests:

Requests / reports.ts

export interface Requests extends Record<keyof Requests, (...args: any[]) => any> {
  // empty here, but merge into this 
}

// a dummy httpCall function
declare function httpCall<R>(path: string, method: string, payload: any): R;

// for now only GET is supported, and the path must be one of keyof Requests
export function createRequest<P extends keyof Requests>(method: "GET", path: P) {
  return (function resourceApiCall(
    ...args: Parameters<Requests[P]> // Parameters<F> is the arg tuple of function type F
  ): ReturnType<Requests[P]> {
    // ReturnType<F> is the return type of function type F
    return httpCall<ReturnType<Requests[P]>>(path, method, args);
  } as any) as Requests[P]; // assertion to clean up createRequest signature
}

Затем вы можете сделать модуль для вашего Userвещи:

запросы / user.ts

export interface User {
  name: string;
  age: number;
}
declare module './requests' {
  interface Requests {
    "/users": (clause: { createdAfter: Date }) => Promise<Array<User>>;
  }
}

и ваши Payment материалы:

запросы / payment.ts

export interface Payment {
  id: string;
}
declare module './requests' {
  interface Requests {
    "/payments": (id: string, clause: { createdAfter: Date }) => Promise<Payment>;
  }
}

и так далее.Наконец, пользователь может вызвать их, импортировав createRequest и, возможно, модули user и payment (если в них есть код, который вам нужно запустить в своем модуле):

test.ts

import { createRequest } from './Requests/requests';
import './Requests/user'; // maybe not necessary
import './Requests/payment'; // maybe not necessary

async function foo() {
  const fetchUsers = createRequest("GET", "/users");
  const users = await fetchUsers({ createdAfter: new Date() }); // User[]
  const fetchPayment = createRequest("GET", "/payments");
  const payment = await fetchPayment("id", { createdAfter: new Date() }); // Payment
}

Хорошо, надеюсь, это поможет снова.

0 голосов
/ 22 мая 2019

Вы можете комбинировать псевдонимы и перегрузки, чтобы заставить это работать. По сути, псевдоним этих аргументов как строковых литералов, а затем дать вашей функции несколько подписей. Затем TypeScript может вывести тип возврата createRequest на основе аргументов, переданных в

type UserPath = '/users';
type PaymentPath = '/payment';
type CreatedAfter = {
  createdAfter: Date;
};

function createRequest(
  HttpVerb: string,
  target: UserPath
): (id: string, date: CreatedAfter) => Promise<{ id: string }>;

function createRequest(
  HttpVerb: string,
  target: PaymentPath
  //I'm just guessing the return type here
): (date: CreatedAfter) => Promise<{ id: string }[]>; 

function createRequest(HttpVerb: string, target: UserPath | PaymentPath): any {
  //your function implementation doesn't have to be like this, this is just so
  //this example is fully working
  if (target === '/users') {
    return async function(date) {
      return { id: '1' };
    };
  } else if (target === '/payment') {
    return async function(id, date) {
      return [{ id: '1' }];
    };
  }
}

//this signature matches your fetchUsers signature
const fetchUsers = createRequest('GET', '/users'); 

//this signature matches your fetchPayment signature
const fetchPayment = createRequest('GET', '/payment');

Суммируя, это позволит функции createRequest возвращать функцию с правильной сигнатурой на основе второго переданного аргумента. Подробнее о сигнатурах функций читайте здесь , ctrl + f и ищите «Перегрузки», чтобы узнать больше о перегрузках.

...