vue知识点之使用ts封装axios

一、封装思路

有了一个清晰的请求流程图,我们便可以区分出来两块重要的内容来进行拆分: 基础请求流程 、 拦截器 。

基础请求流程

基础请求流程,我们大致可以分为三块, 一是 请求进入请求拦截前 、二是 真正发起的请求 、三是 请求从响应拦截出来后

这其中可以归为两类,

☘️ 一类是 针对单独接口的处理

1. 请求前的参数处理;
2. 请求后的返回值处理;

☘️ 二类是 针对所有接口需要的内容

1. Post;
2. Get;
3. Put;
4. Del

拦截器

拦截器,我们大致可以分为两类, 一类是 请求接口前的统一处理(请求拦截) 、 一类是 请求接口后的统一处理(响应拦截)

☘️ 一类是 请求拦截

1. 请求调整
2. 用户标识

☘️ 二类是 响应拦截

1. 网络错误处理
2. 授权错误处理
3. 普通错误处理
4. 代码异常处理

二、开始封装

1. 针对所有接口的处理(Get)

我们希望以 const [e, r] = await api.getUserInfo(id) 的方式调用,代表着我们需要保证返回值稳定的返回 [err, result] ,所以我们需要在请求无论成功失败时,都以 resolve 方式调用。

同时,我们希望我们可以处理返回值,因此在这里封装了 clearFn 的回调函数。

type Fn = (data: FcResponse<any>) => unknown

interface IAnyObj {
  [index: string]: unknown
}

interface FcResponse<T> {
  errno: string
  errmsg: string
  data: T
}

const get = <T,>(url: string, params: IAnyObj = {}, clearFn?: Fn): Promise<[any, FcResponse<T> | undefined]> =>
  new Promise((resolve) => {
    axios
      .get(url, { params })
      .then((result) => {
        let res: FcResponse<T>
        if (clearFn !== undefined) {
          res = clearFn(result.data) as unknown as FcResponse<T>
        } else {
          res = result.data as FcResponse<T>
        }
        resolve([null, res as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })

2. 请求拦截

请求拦截中,我们需要两块内容,一是 请求的调整 ,二是 配置用户标识

//1. 请求的调整(处理自定义请求头)
const handleRequestHeader = (config) => {
    config['xxxx'] = 'xxx'
    return config
}
//2. 配置用户标识(处理鉴权)
const handleAuth = (config) => {
    config.header['token'] = localStorage.getItem('token') || token || ''
    return config
}
//请求拦截
axios.interceptors.request.use((config) => {
    config = handleChangeRequestHeader(config)
    config = handleConfigureAuth(config)
    return config
})

3. 响应拦截

响应错误由三类错误组成:

  • 网络错误处理
  • 授权错误处理
  • 普通错误处理

因此,要优雅的处理响应拦截,我们必须先将三类错误函数写好,以便于我们增强代码扩展性及后期维护。

☘️ 网络错误处理
const handleNetworkError = (errStatus) => {
    let errMessage = '未知错误'
    if (errStatus) {
        switch (errStatus) {
            case 400:
                errMessage = '错误的请求'
                break
            case 401:
                errMessage = '未授权,请重新登录'
                break
            case 403:
                errMessage = '拒绝访问'
                break
            case 404:
                errMessage = '请求错误,未找到该资源'
                break
            case 405:
                errMessage = '请求方法未允许'
                break
            case 408:
                errMessage = '请求超时'
                break
            case 500:
                errMessage = '服务器端出错'
                break
            case 501:
                errMessage = '网络未实现'
                break
            case 502:
                errMessage = '网络错误'
                break
            case 503:
                errMessage = '服务不可用'
                break
            case 504:
                errMessage = '网络超时'
                break
            case 505:
                errMessage = 'http版本不支持该请求'
                break
            default:
                errMessage = `其他连接错误 --${errStatus}`
        }
    } else {
        errMessage = `无法连接到服务器!`
    }
    message.error(errMessage)
}
☘️ 授权错误处理
const handleAuthError = (errno) => {
    const authErrMap: any = {
      '10031': '登录失效,需要重新登录', // token 失效
      '10032': '您太久没登录,请重新登录~', // token 过期
      '10033': '账户未绑定角色,请联系管理员绑定角色',
      '10034': '该用户未注册,请联系管理员注册用户',
      '10035': 'code 无法获取对应第三方平台用户',
      '10036': '该账户未关联员工,请联系管理员做关联',
      '10037': '账号已无效',
      '10038': '账号未找到',
    }
    
    if (authErrMap.hasOwnProperty(errno)) {
        message.error(authErrMap[errno])
        // 授权错误,登出账户
        logout()
        return false
    }

    return true
}
☘️ 普通错误处理
const handleGeneralError = (errno, errmsg) => {
    if (err.errno !== '0') {
        meessage.error(err.errmsg)
        return false
    }
    return true
}

适配

当我们将所有的错误类型处理函数写完,在 axios 的拦截器中进行调用即可。

axios.interceptors.response.use(
    (response) => {
       if (response.status !== 200) return Promise.reject(response.data)
      //授权错误
        handleAuthError(response.data.errno)
      //普通错误
        handleGeneralError(response.data.errno, response.data.errmsg)
        return response
    },
    (err) => {
     //网络错误
        handleNetworkError(err.response.status)
        Promise.reject(err.response)
    }
)

4. 针对单独接口的处理

基于上面的几类通用处理,我们这个请求的封装基本已经可用了。

但是我们还有一些额外的操作无处存放(参数处理、返回值处理),且我们并不想将他们耦合在页面中每次调用进行处理,那么我们显然需要一个位置来处理这些内容。

import { Get } from "../server"

interface FcResponse<T> {
      errno: string
      errmsg: string
      data: T
}

type ApiResponse<T> = Promise<[any, FcResponse<T> | undefined]>

function getUserInfo<T extends { id: string; name: string; }>(id): ApiResponse<T> {
      return Get<T>('/user/info', { userid: id })
}

5. 封包处理

☘️ 接口分类封包
  • 用户数据: api/path/user.ts
import { Get } from "../server"
export function getUserInfo(id) { ... }
export function getUserName(id) { ... }
export const userApi = {
    getUserInfo,
    getUserName
}
  • 订单数据: api/path/shoporder.ts
import { Get } from "../server"
function getShoporderDetail() { ... }
function getShoporderList() { ... }
export const shoporderApi = {
    getShoporderDetail,
    getShoporderList
}
☘️ 调用点统一
  • api/index.ts
import { userApi } from "./path/user"
import { shoporderApi } from "./path/shoporder"
export const api = {
    ...userApi,
    ...shoporderApi
}
☘️ 针对所有接口的处理(Post、Put、Del)
export const post = <T,>(url: string, data: IAnyObj, params: IAnyObj = {}): Promise<[any, FcResponse<T> | undefined]> => {
  return new Promise((resolve) => {
    axios
      .post(url, data, { params })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })
}

// Put / Del 同理

三、完整代码

git地址: github.com/Sincenir/si…

☘️ 业务处理函数: src/api/tool.ts
const handleRequestHeader = (config) => {
    config['xxxx'] = 'xxx'
    return config
}

const handleAuth = (config) => {
    config.header['token'] = localStorage.getItem('token') || token || ''
    return config
}
const handleNetworkError = (errStatus) => {
    let errMessage = '未知错误'
    if (errStatus) {
        switch (errStatus) {
            case 400:
                errMessage = '错误的请求'
                break
            case 401:
                errMessage = '未授权,请重新登录'
                break
            case 403:
                errMessage = '拒绝访问'
                break
            case 404:
                errMessage = '请求错误,未找到该资源'
                break
            case 405:
                errMessage = '请求方法未允许'
                break
            case 408:
                errMessage = '请求超时'
                break
            case 500:
                errMessage = '服务器端出错'
                break
            case 501:
                errMessage = '网络未实现'
                break
            case 502:
                errMessage = '网络错误'
                break
            case 503:
                errMessage = '服务不可用'
                break
            case 504:
                errMessage = '网络超时'
                break
            case 505:
                errMessage = 'http版本不支持该请求'
                break
            default:
                errMessage = `其他连接错误 --${errStatus}`
        }
    } else {
        errMessage = `无法连接到服务器!`
    }
    message.error(errMessage)
}

const handleAuthError = (errno) => {
    const authErrMap: any = {
      '10031': '登录失效,需要重新登录', // token 失效
      '10032': '您太久没登录,请重新登录~', // token 过期
      '10033': '账户未绑定角色,请联系管理员绑定角色',
      '10034': '该用户未注册,请联系管理员注册用户',
      '10035': 'code 无法获取对应第三方平台用户',
      '10036': '该账户未关联员工,请联系管理员做关联',
      '10037': '账号已无效',
      '10038': '账号未找到',
    }
    
    if (authErrMap.hasOwnProperty(errno)) {
        message.error(authErrMap[errno])
        // 授权错误,登出账户
        logout()
        return false
    }
    return true
}

const handleGeneralError = (errno, errmsg) => {
    if (err.errno !== '0') {
        meessage.error(err.errmsg)
        return false
    }
    return true
}
☘️ 通用操作封装: src/api/server.ts
import axios from 'axios'
import { message } from 'antd'

import {
    handleChangeRequestHeader,
    handleConfigureAuth,
    handleAuthError,
    handleGeneralError,
    handleNetworkError
} from './tools'

type Fn = (data: FcResponse<any>) => unknown

interface IAnyObj {
    [index: string]: unknown
}

interface FcResponse<T> {
    errno: string
    errmsg: string
    data: T
}

axios.interceptors.request.use((config) => {
  config = handleChangeRequestHeader(config)
    config = handleConfigureAuth(config)
    return config
})

axios.interceptors.response.use(
    (response) => {
        if (response.status !== 200) return Promise.reject(response.data)
        handleAuthError(response.data.errno)
        handleGeneralError(response.data.errno, response.data.errmsg)
        return response
    },
    (err) => {
        handleNetworkError(err.response.status)
        Promise.reject(err.response)
    }
)

export const Get = <T,>(url: string, params: IAnyObj = {}, clearFn?: Fn): Promise<[any, FcResponse<T> | undefined]> =>
  new Promise((resolve) => {
    axios
      .get(url, { params })
      .then((result) => {
        let res: FcResponse<T>
        if (clearFn !== undefined) {
          res = clearFn(result.data) as unknown as FcResponse<T>
        } else {
          res = result.data as FcResponse<T>
        }
        resolve([null, res as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })

export const Post = <T,>(url: string, data: IAnyObj, params: IAnyObj = {}): Promise<[any, FcResponse<T> | undefined]> => {
  return new Promise((resolve) => {
    axios
      .post(url, data, { params })
      .then((result) => {
        resolve([null, result.data as FcResponse<T>])
      })
      .catch((err) => {
        resolve([err, undefined])
      })
  })
}
☘️ 统一调用点: src/api/index.ts
import { userApi } from "./path/user"
import { shoporderApi } from "./path/shoporder"

export const api = {
    ...userApi,
    ...shoporderApi
}
☘️ 接口: src/api/path/user.ts | src/api/path/shoporder.ts
  • src/api/path/user.ts
import { Get } from "../server"
export function getUserInfo(id) { ... }
export function getUserName(id) { ... }
export const userApi = {
    getUserInfo,
    getUserName
}
  • src/api/path/shoporder.ts
import { Get } from "../server"
function getShoporderDetail() { ... }
function getShoporderList() { ... }
export const shoporderApi = {
    getShoporderDetail,
    getShoporderList
}

posted on 2024-08-28 19:16  梁飞宇  阅读(33)  评论(0)    收藏  举报