跳到主要内容

TypeScript实战应用

· 阅读需 5 分钟
Jason Rong
前端糕手

TS类型体操

前言

本文借助axios请求封装作为实战用例 涉及内容: TypeScriptaxiosReact

核心代码

import axios from 'axios';
import type { AxiosResponse, AxiosRequestConfig } from 'axios';
import { toast } from 'react-toastify';
interface IData<T = any> {
success: boolean;
message: string;
data?: T;
token?: string;
}
const baseURL = '/api';
const Axios = axios.create({
baseURL
});

Axios.interceptors.request.use(
config => {
if (config.url === '/user/admin') {
return config;
} else {
const token = localStorage.getItem('token');
if (!token) {
toast.warn('请先登录', { progress: 1 });
setTimeout(() => {
window.location.href = '/';
}, 2500);
return Promise.reject('请先登录');
} else {
config.headers.Authorization = token;
return config;
}
}
},
error => {
toast.error(error);
return Promise.reject(error);
}
);
Axios.interceptors.response.use(
(response: AxiosResponse<IData, any>) => {
const { data } = response;
if (data.success) {
return response;
} else {
toast.error(data.message);
return Promise.reject(data.message);
}
},
error => {
console.log(error);
toast.error(error.message);
return Promise.reject(error);
}
);
async function request<T>(config: AxiosRequestConfig): Promise<IData<T>> {
const response = await Axios.request<IData<T>>(config);
return response.data;
}
export default request;

axios类型刨析

axios涉及的api太多,我就聚焦在核心代码中涉及到的

AxiosRequestConfig

export interface AxiosRequestConfig<D = any> {
url?: string;
method?: Method | string;
baseURL?: string;
transformRequest?: AxiosRequestTransformer | AxiosRequestTransformer[];
transformResponse?: AxiosResponseTransformer | AxiosResponseTransformer[];
headers?: (RawAxiosRequestHeaders & MethodsHeaders) | AxiosHeaders;
params?: any;
data?: D;
timeout?: Milliseconds;
timeoutErrorMessage?: string;
withCredentials?: boolean;
adapter?: AxiosAdapterConfig | AxiosAdapterConfig[];
auth?: AxiosBasicCredentials;
responseType?: ResponseType;
}

上面是AxiosRequestConfig的定义,我仅截取了一部分作为说明。

这里我们可以看出接口定义了一个D泛型来约束我们所传入data的类型,并且默认它为any类型除非我们显式约束它。

AxiosResponse

export interface AxiosResponse<T = any, D = any> {
data: T;
status: number;
statusText: string;
headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
config: InternalAxiosRequestConfig<D>;
request?: any;
}

这里接受两个泛型,分别表示dataconfig的类型,默认它们为any类型。 其中InternalAxiosRequestConfigAxiosRequestConfig的拓展

 export interface InternalAxiosRequestConfig<D = any> extends AxiosRequestConfig<D> {
headers: AxiosRequestHeaders;
}

思路

首先,明确我们的目的:

  1. 能够对接口请求进行统一请求、响应拦截.
  2. 编写请求代码时能够约束或者说定义接口所返回的数据结构
  3. 大部分情况下TS能够提供较好的类型提示

请求拦截

设置了权限拦截,对没有通过身份校验的人员退回验证界面。不是本文的重点,就不赘述了。

响应拦截⭐

interface IData<T = any> {
success: boolean;
message: string;
data?: T;
token?: string;
}
(response: AxiosResponse<IData, any>) => {
const { data } = response;
if (data.success) {
return response;
} else {
toast.error(data.message);
return Promise.reject(data.message);
}
},
error => {
console.log(error);
toast.error(error.message);
return Promise.reject(error);
}

上面我们提到了AxiosResponse接受两个泛型,分别表示dataconfig的类型,默认它们为any类型。 我们这里仅对data进行约束,所以我们将IData作为AxiosResponse的第一个泛型传入。 IData是服务端响应数据的结构,它也接受一个泛型,用来约束data字段的数据结构。

导出封装后的请求方法⭐⭐⭐

async function request<T>(config: AxiosRequestConfig): Promise<IData<T>> {
const Response = await Axios.request<IData<T>>(config);
return Response.data;
}
export default request;

后续的接口请求都通过request去完成 使用案例:

import request from '.';
function addProduct<T = any>(formData: FormData) {
return request<T>({ url: '/product/add', data: formData, method: 'post' });
}
function getProductList<T = any>() {
return request<T>({ url: '/product/list', method: 'get' });
}
export { addProduct,getProductList };

const response = await addProduct<null>(formData); //这里<null>其实就是data的类型,其他类型都是固定不变的
console.log(response.message)
console.log(response.data) //null

注意项⭐⭐

要注意我们在导出的request方法是做了一层数据解构,只返回了AxiosResponse中的data字段,也就是实际上后续的请求方法获取到的数据是data字段的数据,Responseresponse并不一样。大部分情况我们都是直接使用服务端返回给我们的数据结构去完成业务逻辑。而解构后的response就是服务端返回的数据。

服务端返回的数据

 response.json({success: true, message: '商品添加成功'});