TypeScript接口参数/响应类型自动推导
TypeScript Web 项目的API 的参数与响应数据类型,如果不手动映射,默认是缺失的:
async function sendRequest(url: string, params?: any) { const response = axios.get(url, { params }) return response // -> Promise<AxiosResponse<any, any>> } 复制代码
这给项目带来了少许不稳定性。如果复杂的话,每个接口的响应数据都是 any
,各种接口/返回数据互相依赖,可想其混乱程度。
以下通过编写一个通用的请求函数 sendRequest
来实现(跳转实际效果示例):
指定响应类型
查看 axios 的类型,可知是支持制定接口响应类型的:
export class Axios { get<T = any, R = AxiosResponse<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>; } 复制代码
具体做法是指定泛型 T参数,来让 TS 推导出响应数据类型,修改初始代码:
// 假定接口A的路径是 '/apple', 响应类型是 AppleRes interface AppleRes { code: number data: string } async function sendRequest<T>(url: string, params?: any) { const response = axios.get<T>(url, { params }) return response } const apple = sendRequest<AppleRes>('/apple') // -> Promise<AxiosResponse<AppleRes, any>> apple.then((res) => { const blah = res.data.data // -> string const blah2 = res.data.data2 // Error: Property 'data2' does not exist on type 'AppleRes'. Did you mean 'data'? }) 复制代码
这时候TS能够推导响应类型了, 当我们输入不存在的属性的时候,TS提示属性不存在。
可是至此好像并没有很大的帮助,毕竟我们也可以在请求编写 as AppleRes
映射类型,下面继续。
指定参数类型
映射参数类型是简单的, 只需要在 params 参数指定:
// 假定接口A的路径是 '/apple', 参数类型是 AppleReq, 响应类型是 AppleRes interface AppleReq { pageNum: number pageSize?: number } async function sendRequest<T, R>(url: string, params?: R) { const response = axios.get<T>(url, { params }) return response } const apple = sendRequest<AppleRes, AppleReq>('/apple', { pageNum: 1, // -> number blah: 1 // Error: Argument of type '{ pageNum: number; blah: number; }' is not assignable to parameter of type 'AppleReq'. }) 复制代码
这样,如果我们输入错了参数,TS也能够纠正。
可是,貌似还是不够。这样的话,每次请求接口都需要手动输入 Req, Res 的类型,很麻烦。
有没有一个方法可以输入 sendRequest('/apple')
请求路径的时候, 就能够让 TS 推导请求&响应数据的类型呢?
绑定请求路径&参数&响应数据类型
假定我们有很多个接口,我们一一定义它们的映射关系,使用 interface 挺合适:
interface AppleRes { code: number data: string } interface AppleReq { pageNum: number } interface BananaRes { code: number data: object } interface BananaReq { pageSize: number } //... // 关键: 在 ApiMaps 绑定它们的映射关系 interface ApiMaps { '/apple': { req: AppleReq; res: AppleRes } '/banana': { req: BananaReq; res: BananaRes } '/cat': { req: CatReq; res: CatRes } } 复制代码
很多企业都有内部的接口管理平台,YY的是 Tagee 平台。社区版本也有,如 Swagger,Rap2等,以上部分可以通过接口管理平台轻松批量生成。
这样的话我们可以通过 '/apple'
这个键来获得这个路径的请求和响应类型:
type AppleApiMap = ApiMaps['/apple'] // 等价于: type AppleApiMap = { req: AppleReq; res: AppleRes; } 复制代码
然后,我们在 sendRequest
映射:
// 获得请求路径的类型集合: type ApiKeys = keyof ApiMaps async function sendRequest<T extends ApiKeys = ApiKeys>(url: T, params?: ApiMaps[T]['req']) { const response = await axios.get<ApiMaps[T]['res']>(url, { params }) return response } 复制代码
说明: T extends ApiKeys = ApiKeys
表示以上泛型 T 是 ApiKeys
集合中的一个,即 '/apple'
, '/banana'
, '/cat'
其一。
= ApiKeys
则是泛型默认值,如果我们没有传入泛型参数时候,TS可以使用实际传入参数的类型作为默认类型。可参考:TypeScript: Documentation - TypeScript 2.3 (typescriptlang.org)
实际效果
const apple = sendRequest('/apple', { pageNum: 1 }) apple.then((res) => { const blah = res.data.data // -> string const blah2 = res.data.data2 // Error: Property 'data2' does not exist on type 'AppleRes'. Did you mean 'data'? }) const banana = sendRequest('/banana', { pageSize: 1 }) banana.then((res) => { const blah = res.data.data // -> boolean }) 复制代码
在 VSCode中还会自动提示有什么路径,类型可选:
作者:ziolau
链接:https://juejin.cn/post/7038900258461450254
伪原创工具 SEO网站优化 https://www.237it.com/