如何给你的接口返回值加上类型提示
接口类型提示及HTTP Client https://www.zhihu.com/video/1634225537072713728
如今随着 typescript 的流行,我们在开发过程中得到了很多收益,前端很多项目在做技术选型时对库的选择有很个很重要的指标就是这个库有没有ts类型定义,好的类型定义能极大的降低上手一个新库的学习成本。
同样,相信前端的朋友们在对接后端接口时也希望能有个实时的接口类型提示,比如
axiosInstance.get('/pets/{id}').then(res => {
res.data // data type is Pet
其中,获取
/v1/some/url
的资源时,res的类型为
AxiosResponse
类型, 这个类型大致包含如下字段
interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
config: InternalAxiosRequestConfig<D>;
request?: any;
}
在实际业务中,data的类型通常需要手动定义,然后在get时指定这个类型
interface Pet {
* 宠物名
name: string;
id: string;
* 宠物标签
tag?: string;
axiosInstance.get<Dog>('/pets/{id}').then(res => {
res.data.name // data type is Pet
})
这样虽然可以达到目的,但使用起来还是不太方便,如果可以在给定url时,
res.data
自动映射成
Dog
类型,不是可以省了手动定义的麻烦吗?
如果要根据url自动映射类型,那么
Dog
类型要从哪里获取呢?其实
openapi
已经替我们解决了这个问题。
在项目接口对接的过程中,后端通常会提供一个 swagger ui 页面,而这个页面的数据是根据一个固定格式的json 文件渲染出来的,这个文件的内容大致如下
// type-define.json
{
"openapi": "3.0.0",
"info": {
//... etc
"servers": [ //... etc
"paths": {
"/pets": {
"get": {
"parameters": [
// ...etc
"responses": {
"200": {
"description": "pet response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Pet"
"components": {
"schemas": {
"Pet": {
"allOf": [
"$ref": "#/components/schemas/NewPet"
"type": "object",
"required": [
"properties": {
"id": {
"type": "integer",
"format": "int64"
"NewPet": {
"type": "object",
"required": [
"name"
"properties": {
"name": {
"type": "string"
"tag": {
"type": "string"
"Error": {
"type": "object",
"required": [
"code",
"message"
"properties": {
"code": {
"type": "integer",
"format": "int32"
"message": {
"type": "string"
}
在路径
paths['pets']['get']['responses']['200']['content']['application/json']
中可以找到我们想要的
Pet
类型,但是,这是json格式,并不是我们想要的ts类型,这里通过一个
openapi-typescript
库把json转换为如下的ts类型定义
// type-define.ts
export interface paths {
"/pets/{id}": {
/** @description Returns a user based on a single ID, if the user does not have access to the pet */
get: operations["find pet by id"];
export type webhooks = Record<string, never>;
export interface components {
schemas: {
Pet: components["schemas"]["NewPet"] & {
/** Format: int64 */
id: number;
NewPet: {
name: string;
tag?: string;
Error: {
/** Format: int32 */
code: number;
message: string;
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
export type external = Record<string, never>;
export interface operations {
/** @description Returns a user based on a single ID, if the user does not have access to the pet */
"find pet by id": {
parameters: {
path: {
/** @description ID of pet to fetch */
id: number;
responses: {
/** @description pet response */
200: {
content: {
"application/json": components["schemas"]["Pet"];
/** @description unexpected error */
default: {
content: {
"application/json": components["schemas"]["Error"];
}
此时,引用上面的ts类型定义就可以开心coding了
import { path } from './type-define'
axiosInstance.get<path['/pets/{id}']['get']['responses']['200']['content']['application/json']>('/pets/{id}').then(res => {
res.data // data type is Pet
})
但是,等等,上面这一长串的路径引用实在是太不美观了吧,虽然可以定义类型别名来缓解,但是还是让人无法接受,我们一开始的目的是自动的类型映射啊。
所以,接下来,在已经有了完善的类型定义的前提下,要怎么把上面这一长串的类型引用省略掉呢?
现在,就轮到强大的 ts 类型体操发挥作用了。在解决一个问题时,通常需要把这个问题简化一下,假如我们需要的Pet类型的并没有隐藏的这么深,只是一层路径,那么我们就可以使用Extract来轻松的提取它
export interface paths {
"/pet/{id}": {
get: {
/** Format: int64 */
id: number;
name: string;
tag?: string;