使用场景
在前端开发过程中,使用http/https
请求到服务端获取数据基本是必备场景了。
在这里使用ts
、axios
和antd
的notification
做了接口请求封装,可根据实际需求再做修改。
封装中包含基于token
的登录验证,对请求响应的错误处理,以及对重复请求的缓存策略。
封装过程
创建axios实例
ts
const BASE_URL = '/api' // 根据实际需求更改
const http: AxiosInstance = axios.create({
baseURL: BASE_URL,
timeout: 10000,
headers: { 'Content-Type': 'application/json' },
})
使用拦截器做请求及响应的数据处理
请求拦截器
请求拦截器中主要处理用户登录token,如果能够获取到token,则将token传到请求headers对应字段中。
ts
http.interceptors.request.use(
(config) => {
// 这里可以根据是否需要清除空白参数做调整
if (config.params) config.params = deleteNullParams(config.params)
if (config.data) config.data = deleteNullParams(config.data)
// 如需登录验证,可以在这里添加headers的token验证
const token = useSystemStore.getState().token
if (token) config.headers['Authorization'] = token
return config
},
(error) => {
return Promise.reject(error)
}
)
function deleteNullParams(params: { [key: string]: unknown }) {
for (const key in params) {
if (params[key] === null || params[key] === undefined || params[key] === '') {
delete params[key]
}
}
return params
}
响应拦截器
响应拦截器主要处理请求响应错误数据。包含http
请求本身的错误以及服务返回数据内的自定义数据错误。
ts
// 响应数据结构定义
export interface ApiRes<T> {
code: number
msg: string
data: T
}
http.interceptors.response.use(
<T>(response: AxiosResponse<ApiRes<T>>) => {
// 响应 status 401 表示登录token验证失败,需要清除token并跳转到登录页面
if (response.status === 401) {
useSystemStore.getState().clear()
location.href = '/login'
return Promise.reject('Unauthorized access, please login again.')
}
// 响应 status 不为 200 表示请求失败
if (response.status !== 200) {
showNotify()
return Promise.reject(response.statusText)
}
// 接口数据返回自定义错误处理
const { code, data, msg } = response.data
if (code !== 200) {
showNotify(code, msg)
return Promise.reject(msg)
}
return data
},
(error) => {
showNotify(error.code, error.message)
return Promise.reject(error.message)
}
)
请求缓存
请求缓存主要是解决重复请求问题,同样一个请求如果连续发起多次,第一次会正常请求,并将请求的返回Promise
保存到缓存中,后面再发起请求时,如果第一次发起的请求还没有返回数据(缓存还在),则将缓存直接作为该次请求的返回数据,无需发起重复请求。
ts
const resPromiseCache = new Map<string, Promise<unknown>>()
const cachedRequest = <T>(config: AxiosRequestConfig) => {
const key = getUniKey(config)
if (resPromiseCache.has(key)) return resPromiseCache.get(key) as Promise<T>
const promise = http.request<T>(config)
resPromiseCache.set(key, promise)
promise.finally(() => resPromiseCache.delete(key))
return promise as Promise<T>
}
function getUniKey(config: AxiosRequestConfig): string {
const { method, url, params, data } = config
return `${method}-${url}-${JSON.stringify(params)}-${JSON.stringify(data)}`
}
常用请求方法封装
这里封装了常用的GET
、POST
、PUT
、DELETE
快捷请求函数
ts
export const get = <T>(url: string, params?: object): Promise<T> => {
return cachedRequest<T>({ method: 'get', url, params })
}
export const post = <T>(url: string, data?: object): Promise<T> => {
return cachedRequest<T>({ method: 'post', url, data })
}
export const put = <T>(url: string, data?: object): Promise<T> => {
return cachedRequest<T>({ method: 'put', url, data })
}
export const del = <T>(url: string, params?: object, data?: object): Promise<T> => {
return cachedRequest<T>({ method: 'delete', url, params, data })
}
完整示例
ts
import axios from 'axios'
import type { AxiosInstance, AxiosResponse, AxiosRequestConfig } from 'axios'
import { notification } from 'antd'
import useSystemStore from '@/stores/system'
export interface ApiRes<T> {
code: number
msg: string
data: T
}
const BASE_URL = '/api'
const resPromiseCache = new Map<string, Promise<unknown>>()
const http: AxiosInstance = axios.create({
baseURL: BASE_URL,
timeout: 10000,
headers: { 'Content-Type': 'application/json' },
})
http.interceptors.request.use(
(config) => {
if (config.params) config.params = deleteNullParams(config.params)
if (config.data) config.data = deleteNullParams(config.data)
const token = useSystemStore.getState().token
if (token) config.headers['Authorization'] = token
return config
},
(error) => {
return Promise.reject(error)
}
)
http.interceptors.response.use(
<T>(response: AxiosResponse<ApiRes<T>>) => {
if (response.status === 401) {
useSystemStore.getState().clear()
location.href = '/login'
return Promise.reject('Unauthorized access, please login again.')
}
if (response.status !== 200) {
showNotify()
return Promise.reject(response.statusText)
}
const { code, data, msg } = response.data
if (code !== 200) {
showNotify(code, msg)
return Promise.reject(msg)
}
return data
},
(error) => {
showNotify(error.code, error.message)
return Promise.reject(error.message)
}
)
const cachedRequest = <T>(config: AxiosRequestConfig) => {
const key = getUniKey(config)
if (resPromiseCache.has(key)) return resPromiseCache.get(key) as Promise<T>
const promise = http.request<T>(config)
resPromiseCache.set(key, promise)
promise.finally(() => resPromiseCache.delete(key))
return promise as Promise<T>
}
const showNotify = (status?: number, description = 'Server Error !') => {
notification.error({
message: `Request Error ${status ? status : ''}`,
description,
})
}
export default http
export const get = <T>(url: string, params?: object): Promise<T> => {
return cachedRequest<T>({ method: 'get', url, params })
}
export const post = <T>(url: string, data?: object): Promise<T> => {
return cachedRequest<T>({ method: 'post', url, data })
}
export const put = <T>(url: string, data?: object): Promise<T> => {
return cachedRequest<T>({ method: 'put', url, data })
}
export const del = <T>(url: string, params?: object, data?: object): Promise<T> => {
return cachedRequest<T>({ method: 'delete', url, params, data })
}
function deleteNullParams(params: { [key: string]: unknown }) {
for (const key in params) {
if (params[key] === null || params[key] === undefined || params[key] === '') {
delete params[key]
}
}
return params
}
function getUniKey(config: AxiosRequestConfig): string {
const { method, url, params, data } = config
return `${method}-${url}-${JSON.stringify(params)}-${JSON.stringify(data)}`
}