阅读 335

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中还会自动提示有什么路径,类型可选:

屏幕录制2021-12-07 17.58.36.gif


作者:ziolau
链接:https://juejin.cn/post/7038900258461450254

 伪原创工具 SEO网站优化  https://www.237it.com/ 


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐