在TypeScript项目中搭配Axios封装后端接口调用

前言

本来是想发 next.js 开发笔记的,结果发现里面涉及了太多东西,还是拆分出来发吧~

本文记录一下在 TypeScript 项目里封装 axios 的过程,之前在开发 StarBlog-Admin 的时候已经做了一次封装,不过那时是 JavaScript ,跟 TypeScript 还是有些区别的。

另外我在跟着 next.js 文档开发的时候,注意到官方文档推荐使用 @tanstack/react-query 来封装请求类的操作,浅看了一下文档之后感觉很不错,接下来我会在项目里实践。

定义配置

先创建一个 global 配置,src/utilities/global.ts

typescript 复制代码
export default class Global {
  static baseUrl = process.env.NEXT_PUBLIC_BASE_URL
}

这是在 next.js 项目,可以用 next 规定的环境变量,其他项目可以自行修改。

封装 auth

认证这部分跟 axios 有点关系,但关系也不是很大,不过因为 axios 封装里面需要用到,所以我也一并贴出来吧。

创建 src/utilities/auth.ts 文件

typescript 复制代码
/**
 * 登录信息
 */
export interface LoginProps {
  token: string
  username: string
  expiration: string
}

/**
 * 认证授权工具类
 */
export default abstract class Auth {
  static get storage(): Storage | null {
    if (typeof window !== 'undefined') {
      return window.localStorage
    }
    return null
  }

  /**
     * 检查是否已登录
     * @return boolean
     */
  public static isLogin() {
    let token = this.storage?.getItem('token')
    let userName = this.storage?.getItem('user')

    if (!token || token.length === 0) return false
    if (!userName || userName.length === 0) return false
    return !this.isExpired();
  }

  /**
     * 检查登录是否过期
     * @return boolean
     */
  public static isExpired = () => {
    let expiration = this.storage?.getItem('expiration')
    if (expiration) {
      let now = new Date()
      let expirationTime = new Date(expiration)
      if (now > expirationTime) return true
    }

    return false
  }

  /**
     * 读取保存的token
     * @return string
     */
  public static getToken = () => {
    return this.storage?.getItem('token')
  }

  /**
     * 保存登录信息
     * @param props
     */
  public static login = (props: LoginProps) => {
    this.storage?.setItem('token', props.token)
    this.storage?.setItem('user', props.username)
    this.storage?.setItem('expiration', props.expiration)
  }

  /**
     * 注销
     */
  public static logout = () => {
    this.storage?.removeItem('token')
    this.storage?.removeItem('user')
    this.storage?.removeItem('expiration')
  }
}

跟认证有关的逻辑我都放在 Auth 类中了

为了在 next.js 中可以愉快使用,还得做一些特别的处理,比如我增加了 storage 属性,读取的时候先判断 window 是否存在。

封装 axios

关于 API 的代码我都放在 src/services 目录下。

创建 src/services/api.ts 文件,代码比较长,分块介绍,可以看到所有配置相比之前 JavaScript 版本的都多了配置,对 IDE 自动补全非常友好。

先 import

typescript 复制代码
import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse, CreateAxiosDefaults} from "axios";
import Global from '@/utilities/global'
import Auth from "@/utilities/auth";

Axios 配置

定义一下 axios 的配置

typescript 复制代码
const config: CreateAxiosDefaults<any> = {
  method: 'get',
  // 基础url前缀
  baseURL: `${Global.baseUrl}/`,
  // 请求头信息
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  },
  // 参数
  data: {},
  // 设置超时时间
  timeout: 10000,
  // 携带凭证
  withCredentials: true,
  // 返回数据类型
  responseType: 'json'
}

统一接口返回值

设置统一的接口返回值,这个和我在 StarBlog 后端里封装的那套是一样的,现在基本是我写后端的标准返回值了,同时也发布了 CodeLab.Share nuget包,可以快捷的引入这个统一的返回值组件。

typescript 复制代码
// 统一接口返回值
export interface ApiResponse {
  data: any
  errorData: any
  message: string
  statusCode: number
  successful: boolean
}

定义 ApiClient

最后就是定义了 ApiClient 类,有点模仿 C# 的 HttpClient 内味了

这里面用到了 axios 的拦截器,发起请求的时候给 header 加上认证信息,返回的时候看看有没有错误,如果是 401 unauthorized 的话就跳转到登录页面。

typescript 复制代码
export class ApiClient {
  private readonly api: AxiosInstance

  constructor() {
    this.api = axios.create({
      ...config,
    })

    this.api.interceptors.request.use(
      config => {
        config.headers.Authorization = `Bearer ${Auth.getToken()}`
        return config
      },
      error => {
        return error
      })

    this.api.interceptors.response.use(
      response => {
        return response
      },
      error => {
        let reason = error
        if (error && error.response) {
          if (error.response.data) {
            reason = error.response.data
            if (!reason.message) reason.message = error.message
          }
          if (error.response.status === 401) {
            location.href = '/login'
          }
        }

        return Promise.reject(reason)
      }
    )
  }

  public request(options: AxiosRequestConfig): Promise<ApiResponse> {
    return new Promise((resolve, reject) => {
      this.api(options).then((res: AxiosResponse<ApiResponse>) => {
        resolve(res.data)
        return false
      }).catch(error => {
        reject(error)
      })
    })
  }
}

export const api = new ApiClient()

export default api

代码比之前我在 StarBlog-Admin 里的简单一些,我要尽可能用较少的代码实现需要的功能。

编写具体接口调用

所有的接口调用我都写成 service (后端思维是这样的)

这里以发短信接口为例

创建 src/services/common.ts 文件,从刚才定义的 api.ts 里面引入 ApiClient 的对象,直接调用 request 方法就完事了。

参数类型是 AxiosRequestConfig ,不对 axios 本身做什么修改,我感觉比之前用 Antd Pro 魔改的接口舒服一些。

typescript 复制代码
import {api} from './api'

export class SmsChannel {
  static local = 0
  static aliyun = 1
  static tencent = 2
}

export default abstract class CommonService {
  public static getSmsCode(phone: string, channel: number = SmsChannel.local) {
    return api.request({
      url: `api/common/getSmsCode`,
      params: {phone, channel}
    })
  }
}

小结

这样封装完比之前 StarBlog-Admin 的舒服很多,可惜之前那个项目用的是 vue2.x 似乎没法用 TypeScript。

就这样吧,大部分内容还是在 next.js 开发笔记中。

参考资料

相关推荐
用户0934077735145 小时前
HarmonyOS WPS Open SDK 实践:用能力分层重构 OpenFileRequest
typescript·harmonyos
gis开发之家1 天前
vue3直击架构痛点 依赖倒置与接口隔离——打造坚如磐石的插件与高阶组件(HOC)模式
javascript·架构·typescript·前端框架·vue3
冬枳1 天前
基于OpenSeagragon封装的前端病理切片展示组件
前端·typescript
退休倒计时1 天前
【每日一题】LeetCode 287. 寻找重复数 TypeScript
算法·leetcode·typescript
@@@@@@@@542 天前
JavaScript 的缺点与 TypeScript 的弥补
开发语言·javascript·typescript
用户0934077735142 天前
HarmonyOS WPS Open SDK 实践:最小打开闭环与 enableEdit 分支处理
typescript·harmonyos
柯克七七3 天前
我把 bug 修得太干净了,测试组以为这个模块本来就没问题
前端·javascript·typescript
退休倒计时3 天前
【每日一题】LeetCode 39. 组合总和 TypeScript
算法·leetcode·typescript
yyt3630458413 天前
KlineChartQuant Tooltip 高频交互 200FPS 性能优化完整复盘
前端·经验分享·性能优化·typescript·vue·交互·chrome devtools
用户0934077735143 天前
HarmonyOS WPS Open SDK 实践:办公审批附件「打开—编辑—回传」代码实现
typescript·harmonyos