返回基于泛型参数的函数签名
Returning a function signature based on generic parameters
我有一个函数createRequest
:
function createRequest(method: string, path: string) {
return function resourceApiCall() {
// ...additional logic
return httpCall(path, method)
}
}
那个 return 是一个函数 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
显示正确的函数参数以及 return 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)
}
}
您可以结合使用别名和重载来实现这一点。基本上将这些参数别名为字符串文字类型,然后为您的函数提供多个签名。然后 TypeScript 可以根据传入的参数
推断出 createRequest
的 return 类型
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
函数根据传递的第二个参数 return 具有正确签名的函数。 Read more about function signatures here、ctrl+f 并搜索 "Overloads" 以了解有关重载的更多信息。
您可以这样进行:
// 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()
到 return。 createRequest()
是函数签名中的 generic function using using Requests
to strongly type the returned function. Notice that inside the implementation of resourceApiCall()
I also use some built-in conditional types to pull the argument types and return 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
东西制作一个模块:
Requests/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
和可能的 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
}
好的,希望再次有所帮助。
我有一个函数createRequest
:
function createRequest(method: string, path: string) {
return function resourceApiCall() {
// ...additional logic
return httpCall(path, method)
}
}
那个 return 是一个函数 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
显示正确的函数参数以及 return 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)
}
}
您可以结合使用别名和重载来实现这一点。基本上将这些参数别名为字符串文字类型,然后为您的函数提供多个签名。然后 TypeScript 可以根据传入的参数
推断出createRequest
的 return 类型
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
函数根据传递的第二个参数 return 具有正确签名的函数。 Read more about function signatures here、ctrl+f 并搜索 "Overloads" 以了解有关重载的更多信息。
您可以这样进行:
// 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()
到 return。 createRequest()
是函数签名中的 generic function using using Requests
to strongly type the returned function. Notice that inside the implementation of resourceApiCall()
I also use some built-in conditional types to pull the argument types and return 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
东西制作一个模块:
Requests/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
和可能的 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
}
好的,希望再次有所帮助。