不是吧, 不是吧, 原来真的有人都 2021 年了, 连 TypeScript 都没听说过吧? 在项目中使用 TypeScript 虽然短期内会增加一些开发成本, 但是对于其需要长期维护的项目, TypeScript 能够减少其维护成本, 使用 TypeScript 增加了代码的可读性和可维护性, 且拥有较为活跃的社区, 当居为大前端的趋势所在, 那就开始淦起来吧~
使用 TypeScript 封装基础 axios 库
代码如下:
- // http.ts
- import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
- import { ElMessage } from "element-plus"
- const showStatus = (status: number) => {
- let message = ''
- switch (status) {
- case 400:
- message = '请求错误 (400)'
- break
- case 401:
- message = '未授权, 请重新登录 (401)'
- break
- case 403:
- message = '拒绝访问 (403)'
- break
- case 404:
- message = '请求出错 (404)'
- break
- case 408:
- message = '请求超时 (408)'
- break
- case 500:
- message = '服务器错误 (500)'
- break
- case 501:
- message = '服务未实现 (501)'
- break
- case 502:
- message = '网络错误 (502)'
- break
- case 503:
- message = '服务不可用 (503)'
- break
- case 504:
- message = '网络超时 (504)'
- break
- case 505:
- message = 'HTTP 版本不受支持 (505)'
- break
- default:
- message = ` 连接出错 (${status})!`
- }
- return `${message}, 请检查网络或联系管理员!`
- }
- const service = axios.create({
- // 联调
- // baseURL: process.env.NODE_ENV === 'production' ? `/` : '/api',
- baseURL: "/api",
- headers: {
- get: {
- 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
- },
- post: {
- 'Content-Type': 'application/json;charset=utf-8'
- }
- },
- // 是否跨站点访问控制请求
- withCredentials: true,
- timeout: 30000,
- transformRequest: [(data) => {
- data = JSON.stringify(data)
- return data
- }],
- validateStatus() {
- // 使用 async-await, 处理 reject 情况较为繁琐, 所以全部返回 resolve, 在业务代码中处理异常
- return true
- },
- transformResponse: [(data) => {
- if (typeof data === 'string' && data.startsWith('{')) {
- data = JSON.parse(data)
- }
- return data
- }]
- })
- // 请求拦截器
- service.interceptors.request.use((config: AxiosRequestConfig) => {
- // 获取 token, 并将其添加至请求头中
- let token = localStorage.getItem('token')
- if(token){
- config.headers.Authorization = `${token}`;
- }
- return config
- }, (error) => {
- // 错误抛到业务代码
- error.data = {}
- error.data.msg = '服务器异常, 请联系管理员!'
- return Promise.resolve(error)
- })
- // 响应拦截器
- service.interceptors.response.use((response: AxiosResponse) => {
- const status = response.status
- let msg = ''
- if (status <200 || status>= 300) {
- // 处理 http 错误, 抛到业务代码
- msg = showStatus(status)
- if (typeof response.data === 'string') {
- response.data = { msg }
- } else {
- response.data.msg = msg
- }
- }
- return response
- }, (error) => {
- if (axios.isCancel(error)) {
- console.log('repeated request:' + error.message)
- } else {
- // handle error code
- // 错误抛到业务代码
- error.data = {}
- error.data.msg = '请求超时或服务器异常, 请检查网络或联系管理员!'
- ElMessage.error(error.data.msg)
- }
- return Promise.reject(error)
- })
- export default service
取消多次重复的请求版本
在上述代码加入如下代码:
- // http.ts
- import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
- import qs from "qs"
- import { ElMessage } from "element-plus"
- // 声明一个 Map 用于存储每个请求的标识 和 取消函数
- const pending = new Map()
- /**
- * 添加请求
- * @param {Object} config
- */
- const addPending = (config: AxiosRequestConfig) => {
- const url = [
- config.method,
- config.url,
- qs.stringify(config.params),
- qs.stringify(config.data)
- ].join('&')
- config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
- if (!pending.has(url)) { // 如果 pending 中不存在当前请求, 则添加进去
- pending.set(url, cancel)
- }
- })
- }
- /**
- * 移除请求
- * @param {Object} config
- */
- const removePending = (config: AxiosRequestConfig) => {
- const url = [
- config.method,
- config.url,
- qs.stringify(config.params),
- qs.stringify(config.data)
- ].join('&')
- if (pending.has(url)) { // 如果在 pending 中存在当前请求标识, 需要取消当前请求, 并且移除
- const cancel = pending.get(url)
- cancel(url)
- pending.delete(url)
- }
- }
- /**
- * 清空 pending 中的请求 (在路由跳转时调用)
- */
- export const clearPending = () => {
- for (const [url, cancel] of pending) {
- cancel(url)
- }
- pending.clear()
- }
- // 请求拦截器
- service.interceptors.request.use((config: AxiosRequestConfig) => {
- removePending(config) // 在请求开始前, 对之前的请求做检查取消操作
- addPending(config) // 将当前请求添加到 pending 中
- let token = localStorage.getItem('token')
- if(token){
- config.headers.Authorization = `${token}`;
- }
- return config
- }, (error) => {
- // 错误抛到业务代码
- error.data = {}
- error.data.msg = '服务器异常, 请联系管理员!'
- return Promise.resolve(error)
- })
- // 响应拦截器
- service.interceptors.response.use((response: AxiosResponse) => {
- removePending(response) // 在请求结束后, 移除本次请求
- const status = response.status
- let msg = ''
- if (status <200 || status>= 300) {
- // 处理 http 错误, 抛到业务代码
- msg = showStatus(status)
- if (typeof response.data === 'string') {
- response.data = { msg }
- } else {
- response.data.msg = msg
- }
- }
- return response
- }, (error) => {
- if (axios.isCancel(error)) {
- console.log('repeated request:' + error.message)
- } else {
- // handle error code
- // 错误抛到业务代码
- error.data = {}
- error.data.msg = '请求超时或服务器异常, 请检查网络或联系管理员!'
- ElMessage.error(error.data.msg)
- }
- return Promise.reject(error)
- })
- export default service
在路由跳转时撤销所有请求
在路由文件 index.ts 中加入
- import { createRouter, createwebHistory, RouteRecordRaw } from 'vue-router'
- import Login from '@/views/Login/Login.vue'
- // 引入在 axios 暴露出的 clearPending 函数
- import { clearPending } from "@/api/axios"
- ....
- ....
- ....
- const router = createRouter({
- history: createWebHistory(process.env.BASE_URL),
- routes
- })
- router.beforeEach((to, from, next) => {
- // 在跳转路由之前, 先清除所有的请求
- clearPending()
- // ...
- next()
- })
- export default router
使用封装的 axios 请求库
封装响应格式
- // 接口响应通过格式
- export interface HttpResponse {
- status: number
- statusText: string
- data: {
- code: number
- desc: string
- [key: string]: any
- }
- }
封装接口方法
举个栗子, 进行封装 User 接口, 代码如下~
- import Axios from './axios'
- import { HttpResponse } from '@/@types'
- /**
- * @interface loginParams - 登录参数
- * @property {string} username - 用户名
- * @property {string} password - 用户密码
- */
- interface LoginParams {
- username: string
- password: string
- }
- // 封装 User 类型的接口方法
- export class UserService {
- /**
- * @description 查询 User 的信息
- * @param {number} teamId - 所要查询的团队 ID
- * @return {HttpResponse} result
- */
- static async login(params: LoginParams): Promise<HttpResponse> {
- return Axios('/api/user', {
- method: 'get',
- responseType: 'json',
- params: {
- ...params
- },
- })
- }
- static async resgister(params: LoginParams): Promise<HttpResponse> {
- return Axios('/api/user/resgister', {
- method: 'get',
- responseType: 'json',
- params: {
- ...params
- },
- })
- }
- }
项目中进行使用
代码如下:
- <template>
- <input type="text" v-model="Account" placeholder="请输入账号" name="username">
- <input type="text" v-model="Password" placeholder="请输入密码" name="username">
- <button @click.prevent="handleRegister()"> 登录 </button>
- </template>
- <script lang="ts">
- import { defineComponent, reactive, toRefs } from 'vue'
- // 引入接口
- import { UserService } from '@/api/user'
- export default defineComponent({
- setup() {
- const state = reactive({
- Account: 'admin', // 账户
- Password: 'hhhh', // 密码
- })
- const handleLogin = async () => {
- const loginParams = {
- username: state.Account,
- password: state.Password,
- }
- const res = await UserService.login(loginParams)
- console.log(res)
- }
- const handleRegister = async () => {
- const loginParams = {
- username: state.Account,
- password: state.Password,
- }
- const res = await UserService.resgister(loginParams)
- console.log(res)
- }
- return {
- ...toRefs(state),
- handleLogin,
- handleRegister
- }
- },
- })
- </script>
来源: https://segmentfault.com/a/1190000039806000