一个基于 TypeScript 和 React-Query 来封装 Axios 的比较好的实践

Published at Reading time 5 minutes

本文将介绍在 TypeScript 下封装 Axios 的一种比较好的实践方式,从而在结合使用 React Query 时有效便捷的实现类型传递。

前置准备

  1. 用 Vite 创建 React + TS 应用

  2. 安装依赖

npm install axios @tanstack/react-query
  1. App.ts 下:
App.ts
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false
}
}
})
const App: FC = () => {
return (
<QueryClientProvider client={queryClient}>
{.....}
</QueryClientProvider>
)
}
export default App

封装Axios

这一步的关键是封装重写axios的 get, post等方法,并传入泛型来约束参数和传递类型,这样便能确定接口返回结果的类型。

新建utils/http.ts文件

http.ts
import axios, {
AxiosError,
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
InternalAxiosRequestConfig
} from 'axios'
// 接口响应类型,根据具体接口结构修改
interface ApiResponse<T> {
code: number
success: boolean
data: T
message?: string
}
class Request {
private instance: AxiosInstance
baseConfig: AxiosRequestConfig = {
baseURL: '/api',
timeout: 60000,
headers: {
Authorization: ''
}
}
constructor(config?: AxiosRequestConfig) {
this.instance = axios.create(Object.assign(this.baseConfig, config))
this.instance.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 处理token
const token = localStorage.getItem('token')
config.headers['Authorization'] = `Bearer ${token}`
return config
},
(err: any) => {
return Promise.reject(err)
}
)
this.instance.interceptors.response.use(
(res: AxiosResponse) => {
if (res.data.code) {
const data = res.data as ApiResponse<any>
if (data.code !== 201) {
data.message && message.error(data.message)
}
return res
}
return res
},
(err: AxiosError) => {
if (err.response) {
const data = err.response.data as ApiResponse<any>
switch (err.response.status) {
case 400:
break
case 401:
break
case 403:
console.warn(`当前没有权限访问,请重新登录!`)
break
case 500:
console.error(`服务器内部错误!${data.message}`)
break
default:
console.error('请求失败')
break
}
}
return Promise.reject(err.response)
}
)
}
public request(config: AxiosRequestConfig): Promise<AxiosResponse> {
return this.instance.request(config)
}
// 重写方法
public get<T = any>(
url: string,
config?: AxiosRequestConfig
): Promise<AxiosResponse<ApiResponse<T>>> {
return this.instance.get(url, config)
}
public post<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<AxiosResponse<ApiResponse<T>>> {
return this.instance.post(url, data, config)
}
public put<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig
): Promise<AxiosResponse<ApiResponse<T>>> {
return this.instance.put(url, data, config)
}
public delete<T = any>(
url: string,
config?: AxiosRequestConfig
): Promise<AxiosResponse<ApiResponse<T>>> {
return this.instance.delete(url, config)
}
public download<T = any>(
url: string,
config?: AxiosRequestConfig
): Promise<AxiosResponse<T>> {
return this.instance.get(url, config)
}
}
export default new Request()

React Query 使用

方法封装完成后,接下来开始使用,以注册和登陆接口为例:

新建api/auth.ts文件:

auth.ts
import http from '@utils/http'
export type LoginParams = { email: string; password: string }
// 登录接口
export const login = async (params: LoginParams) => {
const { data } = await http.post<{ access_token: string }>(
'auth/login',
params
)
return data
}
export type RegisterParams = {
email: string
password: string
username: string
}
// 注册接口
export const register = async (params: RegisterParams) => {
const { data } = await http.post('auth/register', params)
return data
}
export enum Role {
USER = 'USER',
ADMIN = 'ADMIN'
}
export type Profile = {
email: string
role: Role
username: string
avatar: string | null
}
// 获取用户信息接口
export const getProfile = async () => {
const { data } = await http.get<Profile>('auth/profile')
return data
}

这样每个接口返回的 data 属性便有了明确的类型定义,而不用使用 as 等方式去断言类型。

下面以获取用户信息接口为例,下面演示如何与 react query 的 useQuery hooks函数结合使用:

import { FC } from 'react'
import { useQuery } from '@tanstack/react-query'
import { getProfile } from '@/api/auth'
const App: FC = () => {
const { data, isLoading, refetch } = useQuery(
['profile'],
() => getProfile(),
{
onSuccess: (res) => {
console.log('获取信息成功')
}
}
)
if (isLoading) return <div>loading...</div>
return (
<div>
<p>username: {data.username}</p>
<p>email: {data.email}</p>
</div>
)
}

下面再以登录接口为例,下面演示与 useMutation hooks 的结合使用:

import { useState } from 'react'
import { LoginParams, login } from '@api/auth'
import { useMutation } from '@tanstack/react-query'
const LoginPage = () => {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const handleUsernameChange = (event) => {
setUsername(event.target.value);
}
const handlePasswordChange = (event) => {
setPassword(event.target.value);
}
const { isLoading, mutate } = useMutation(
(params: LoginParams) => login(params),
{
onSuccess: (data) => {
if (data.success) {
message.success('登录成功!')
localStorage.setItem('token', data.data.access_token)
navigate('/')
}
}
}
)
const handleSubmit = (event) => {
event.preventDefault()
mutate({
username, password
})
}
return (
<div>
<h2>登陆</h2>
<form>
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<button type="submit" onClick={handleSubmit}>Login</button>
</form>
</div>
)
}
export default LoginPage
Category: TechTags: React,TypeScript