本文将介绍在 TypeScript 下封装 Axios 的一种比较好的实践方式,从而在结合使用 React Query 时有效便捷的实现类型传递。
前置准备
-
用 Vite 创建
React + TS
应用 -
安装依赖
npm install axios @tanstack/react-query
- 在
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: numbersuccess: booleandata: Tmessage?: string}class Request {private instance: AxiosInstancebaseConfig: 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) => {// 处理tokenconst 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:breakcase 401:breakcase 403:console.warn(`当前没有权限访问,请重新登录!`)breakcase 500:console.error(`服务器内部错误!${data.message}`)breakdefault: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: stringpassword: stringusername: 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: stringrole: Roleusername: stringavatar: 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