英文:
Is it possible to provide generic type args by name?
问题
以下是要翻译的内容:
假设有一个通用类型定义,为通用参数提供了命名参数和默认类型,就像axios
库中的这个类型一样:
post<T = any, R = AxiosResponse<T>, D = any>
我可以很容易地指定响应类型,因为它是第一个参数,而其他参数都有默认值:
myClient.post<MyResponseType>(...)
我想要能够指定请求体类型,这是通用类型的D
参数。
但这有点尴尬,因为中间有派生参数R
...我从不想手动指定它。所以我不能这样做:
myClient.post<MyResponseType, MyRequestType>(...)
我必须冗余地拼写出来,如下所示:
myClient.post<MyResponseType, AxiosResponse<MyResponseType>, MyRequestType>(...)
...这样写又冗长又丑陋。
据我所知,在TypeScript中没有简写,例如,我不能这样做:
myClient.post<MyResponseType, D=MyRequestType>(...)
(这似乎引出了为什么要有命名的通用参数的问题)
有没有什么技巧可以让我以一种清晰简洁的方式注释我的post
调用,同时指定请求和响应类型?
英文:
Say there is a generic type defined that provides both named args and default types for the generic args. Like this type from axios
lib:
post<T = any, R = AxiosResponse<T>, D = any>
I can specify the response type quite easily, since it is the first arg and the other args have defaults:
myClient.post<MyResponseType>(...)
I would like to be able to specify the request body type as well, which is the D
arg to the generic type.
But it's awkward because there is the derived param R
in the middle... which I never want to manually specify. So I can't do:
myClient.post<MyResponseType, MyRequestType>(...)
I'd have to redundantly spell it out like:
myClient.post<MyResponseType, AxiosResponse<MyResponseType>, MyRequestType>(...)
...which is long-winded and ugly.
AFAICT there is no shorthand in TS, e.g. I can't do:
myClient.post<MyResponseType, D=MyRequestType>(...)
(which seems to beg the question why bother having named generic args)
Is there any trick I could use to annotate my post
calls with both request and response types in a clean and concise way?
答案1
得分: 1
基于当前的axios/v1.x/index.d.ts
类型定义,你应该能够在自己的.ts
文件中扩展export class Axios
以添加Axios.post
的不同类型参数的重载。
TypeScript,像JavaScript一样,不支持重载函数的实现,但允许重载函数签名,包括重载的通用类型参数。
因此,Axios的index.d.ts
看起来像这样(为简津起见):
export class Axios {
// ...
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
// ...
}
从一个d.ts
文件中消费export class
定义的TypeScript代码可以通过扩展class
的隐式interface
来扩展这些类,因此在你自己的代码中,在.ts
文件的顶部附近,你只需放置类似于以下内容的代码:
declare interface Axios {
post<T, D>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<AxiosResponse<T>>;
}
因为函数参数(url
,data
和config
)以及它的Promise<AxiosResponse<T>>
与底层实现兼容,并且不与index.d.ts
中声明的post
函数发生冲突,所以这是可以的。
...当然,问题是让TypeScript认识到这个interface Axios
实际上是打算合并到导入库中的class Axios
中,并不是一个完全独立的Axios
类型。考虑到模块系统与旧世界全局命名空间的工作方式有所不同,事情变得复杂。
所以...
如果你正在使用import { Axios } from 'axios'
:
// 步骤1. 导入 Axios:
import { Axios, AxiosRequestConfig, AxiosResponse } from 'axios';
// 步骤2. 扩展 Axios:
declare module 'axios' {
interface Axios {
post<T, D>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<AxiosResponse<T>>;
}
}
// 步骤3. 使用 Axios:
interface MyRequestDto {
readonly foobar: string;
}
interface MyResponseDto {
readonly qux: string;
}
async function foo() {
const reqDto: MyRequestDto = { foobar: "hmm" };
const a = new Axios();
// 看,只有两个显式类型参数!
const resp = await a.post<MyResponseDto, MyRequestDto>('/foo', reqDto);
const responseDto: MyResponseDto = resp.data;
console.log(responseDto.qux);
}
如果你正在使用非模块、全局或/// <reference types="axios" />
:
(免责声明:我无法提供完整和可工作的自包含示例,因为我还无法弄清楚如何使/// <reference types="index.d.ts" />
文件的导入工作,但假设你的代码已经正常使用Axios,那么你只需要declare interface Axios
部分)
// 步骤1. 导入 Axios...某种方式:
/// <reference types="axios" />
/// <reference path="axios/index.d.ts" />
/// <reference pleasepleasepleaseworkdamnit="axios" />
// 步骤2. 扩展 Axios:
declare interface Axios {
post<T, D>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<AxiosResponse<T>>;
}
// 步骤3. 使用 Axios:
interface MyRequestDto {
readonly foobar: string;
}
interface MyResponseDto {
readonly qux: string;
}
async function foo() {
const reqDto: MyRequestDto = { foobar: "hmm" };
const a = new Axios();
// 看,只有两个显式类型参数!
const resp = await a.post<MyResponseDto, MyRequestDto>('/foo', reqDto);
const responseDto: MyResponseDto = resp.data;
console.log(responseDto.qux);
}
英文:
Based on the current axios/v1.x/index.d.ts
type defintions, you should be able to just extend export class Axios
in your own .ts
file to add an overload of Axios.post
with different type-parameters.
TypeScript, like JavaScript, doesn't support overloaded function implementations, but it does allow for overloaded function signatures, including overloaded generic type parameters.
So the Axios index.d.ts
has something like this (cut-down for brevity):
export class Axios {
// ...
post<T = any, R = AxiosResponse<T>, D = any>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<R>;
// ...
}
TypeScript code that consumes export class
definitions from a d.ts
can extend those classes by extending the class
's implicit interface
, so in your own code, somewhere near the top of your .ts
file, you'd simply put something like this:
declare interface Axios {
post<T,D>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<AxiosResponse<T>>;
}
Because the function parameters (url
, data
, and config
) and its Promise<AxiosResponse<T>>
are all compatible with the underlying implementation and don't conflict with the declared post
function in index.d.ts
this is fine.
...of course, the problem is getting TypeScript to recognize that this interface Axios
is actually intended to be merged-into-and-extend the class Axios
from the imported library, rather than being a completely separate Axios
type. And things get muddied considering how the module system works differently to the old-world global namespace.
So...
If you're using import { Axios } from 'axios'
:
// Step 1. Import Axios:
import { Axios, AxiosRequestConfig, AxiosResponse } from 'axios';
// Step 2. Extend Axios:
declare module 'axios' {
interface Axios {
post<T,D>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<AxiosResponse<T>>;
}
}
// Step 3. Use Axios:
interface MyRequestDto {
readonly foobar: string;
}
interface MyResponseDto {
readonly qux: string;
}
async function foo() {
const reqDto: MyRequestDto = { foobar: "hmm" };
const a = new Axios();
// Look ma! Only two explicit type-parameters!
const resp = await a.post<MyResponseDto,MyRequestDto>( '/foo', reqDto );
const responseDto: MyResponseDto = resp.data;
console.log( responseDto.qux );
}
If you're using non-module, global, or /// <reference types="axios" />
:
(Disclaimer: I'm not able to provide a complete and working self-contained example because I can't figure out how to get /// <reference types="" />
-style imports of index.d.ts
files to work yet, but provided that your code is already currently using Axios just fine then all you need is the declare interface Axios
part)
// Step 1. Import Axios... somehow:
/// <reference types="axios" />
/// <reference path="axios/index.d.ts" />
/// <reference pleasepleasepleaseworkdamnit="axios" />
// Step 2. Extend Axios:
declare interface Axios {
post<T,D>(url: string, data?: D, config?: AxiosRequestConfig<D>): Promise<AxiosResponse<T>>;
}
// Step 3. Use Axios:
interface MyRequestDto {
readonly foobar: string;
}
interface MyResponseDto {
readonly qux: string;
}
async function foo() {
const reqDto: MyRequestDto = { foobar: "hmm" };
const a = new Axios();
// Look ma! Only two explicit type-parameters!
const resp = await a.post<MyResponseDto,MyRequestDto>( '/foo', reqDto );
const responseDto: MyResponseDto = resp.data;
console.log( responseDto.qux );
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论