根据通用参数返回函数签名

时间:2019-05-22 14:24:03

标签: typescript typescript-typings typescript2.0 typescript1.8 typescript-generics

我有一个函数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,以便fetchUsersfetchPayment在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 个答案:

答案 0 :(得分:1)

您可以这样进行:

// 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()是一个generic函数,使用Requests来强烈键入返回的函数。请注意,在resourceApiCall()的实现中,我还使用了一些内置的conditional typesargument typesreturn type从函数签名中拉出。这不是严格必要的,但是可以使resourceApiCall()内部的键入更加明确。

无论如何,希望能有所帮助。祝你好运!


更新:这是将其拆分为不同模块的一种可能方法,以便每个模块仅接触其自己的端点。

首先,将文件中包含createRequest()以及一个最初为空的Requests接口:

Requests / requests.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东西:

Requests / payment.ts

export interface Payment {
  id: string;
}
declare module './requests' {
  interface Requests {
    "/payments": (id: string, clause: { createdAfter: Date }) => Promise<Payment>;
  }
}
等。最终,用户可以通过导入createRequest以及可能的userpayment模块来调用这些模块(如果它们中包含代码,则需要在模块中运行):

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
}

好的,希望能再有帮助。

答案 1 :(得分:0)

您可以结合使用别名和重载来使其正常工作。基本上,将这些参数作为字符串文字类型的别名,然后为您的函数提供多个签名。然后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函数根据传递的第二个参数返回具有正确签名的函数。 Read more about function signatures here,ctrl + f并搜索“过载”以了解有关过载的更多信息。

相关问题