Vite4+Typescript+Vue3+Pinia 從零搭建(7) - request封裝

来源:https://www.cnblogs.com/weizwz/archive/2023/12/20/17917222.html
-Advertisement-
Play Games

項目代碼同步至碼雲 weiz-vue3-template 基於 axios 封裝請求,支持多功能變數名稱請求地址 安裝 npm i axios 封裝 utils 目錄下新建 request 文件夾,並新建 index.ts、request.ts 和 status.ts 文件。 1. status.ts 文件 ...


項目代碼同步至碼雲 weiz-vue3-template
基於 axios 封裝請求,支持多功能變數名稱請求地址

安裝

npm i axios

封裝

utils 目錄下新建 request 文件夾,並新建 index.tsrequest.tsstatus.ts 文件。

1. status.ts 文件主要是封裝狀態碼

export const ErrMessage = (status: number | string): string => {
  let message: string = ''
  switch (status) {
    case 400:
      message = '請求錯誤!請您稍後重試'
      break
    case 401:
      message = '未授權!請您重新登錄'
      break
    case 403:
      message = '當前賬號無訪問許可權!'
      break
    case 404:
      message = '訪問的資源不存在!請您稍後重試'
      break
    case 405:
      message = '請求方式錯誤!請您稍後重試'
      break
    case 408:
      message = '請求超時!請您稍後重試'
      break
    case 500:
      message = '服務異常!請您稍後重試'
      break
    case 501:
      message = '不支持此請求!請您稍後重試'
      break
    case 502:
      message = '網關錯誤!請您稍後重試'
      break
    case 503:
      message = '服務不可用!請您稍後重試'
      break
    case 504:
      message = '網關超時!請您稍後重試'
      break
    default:
      message = '請求失敗!請您稍後重試'
  }
  return message
}

此時,eslint會報 switch 前面的空格錯誤,需要修改 .eslintrc.cjs 里的 indent,修改後,錯誤消失。

rules: {
  // Switch語句 https://zh-hans.eslint.org/docs/latest/rules/indent#switchcase
  indent: ['error', 2, { SwitchCase: 1 }]
}

2. request.ts 主要是封裝 axios

/**
 * 封裝axios
 * axios 實例的類型為 AxiosInstance,請求需要傳入的參數類型為 AxiosRequestConfig,響應的數據類型為 AxiosResponse,InternalAxiosRequestConfig 繼承於 AxiosRequestConfig
 */
import axios, { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
import { ErrMessage } from './status'

// 自定義請求返回數據的類型
interface Data<T> {
  data: T
  code: string
  success: boolean
}

// 擴展 InternalAxiosRequestConfig,讓每個請求都可以控制是否要loading
interface RequestInternalAxiosRequestConfig extends InternalAxiosRequestConfig {
  showLoading?: boolean
}

// 攔截器
interface InterceptorHooks {
  requestInterceptor?: (config: RequestInternalAxiosRequestConfig) => RequestInternalAxiosRequestConfig
  requestInterceptorCatch?: (error: any) => any
  responseInterceptor?: (response: AxiosResponse) => AxiosResponse
  responseInterceptorCatch?: (error: any) => any
}
// 擴展 AxiosRequestConfig,showLoading 給實例預設增加loading,interceptorHooks 攔截
interface RequestConfig extends AxiosRequestConfig {
  showLoading?: boolean
  interceptorHooks?: InterceptorHooks
}

class Request {
  config: RequestConfig
  instance: AxiosInstance
  loading?: boolean // 用loading指代載入動畫狀態

  constructor(options: RequestConfig) {
    this.config = options
    this.instance = axios.create(options)
    this.setupInterceptor()
  }

  // 類型參數的作用,T決定AxiosResponse實例中data的類型
  request<T = any>(config: RequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      this.instance
        .request<any, Data<T>>(config)
        .then((res) => {
          resolve(res.data)
        })
        .catch((err) => {
          reject(err)
        })
    })
  }

  // 封裝常用方法
  get<T = any>(url: string, params?: object, _object = {}): Promise<T> {
    return this.request({ url, params, ..._object, method: 'GET' })
  }

  post<T = any>(url: string, params?: object, _object = {}): Promise<T> {
    return this.request({ url, params, ..._object, method: 'POST' })
  }

  delete<T = any>(url: string, params?: object, _object = {}): Promise<T> {
    return this.request({ url, params, ..._object, method: 'DELETE' })
  }

  patch<T = any>(url: string, params?: object, _object = {}): Promise<T> {
    return this.request({ url, params, ..._object, method: 'PATCH' })
  }

  put<T = any>(url: string, params?: object, _object = {}): Promise<T> {
    return this.request({ url, params, ..._object, method: 'PUT' })
  }

  // 自定義攔截器 https://axios-http.com/zh/docs/interceptors
  setupInterceptor(): void {
    /**
     * 通用攔截
     */
    this.instance.interceptors.request.use((config: RequestInternalAxiosRequestConfig) => {
      if (config.showLoading) {
        // 載入loading動畫
        this.loading = true
      }
      return config
    })
    // 響應後關閉loading
    this.instance.interceptors.response.use(
      (res) => {
        if (this.loading) this.loading = false
        return res
      },
      (err) => {
        const { response, message } = err
        if (this.loading) this.loading = false
        // 根據不同狀態碼,返回不同信息
        const messageStr = response ? ErrMessage(response.status) : message || '請求失敗,請重試'
        window.alert(messageStr)
        return Promise.reject(err)
      }
    )
    /**
     * 使用通用實例里的攔截,兩個攔截都會生效,返回值以後一個執行的為準
     */
    // 請求攔截
    this.instance.interceptors.request.use(
      this.config?.interceptorHooks?.requestInterceptor,
      this.config?.interceptorHooks?.requestInterceptorCatch
    )
    // 響應攔截
    this.instance.interceptors.response.use(
      this.config?.interceptorHooks?.responseInterceptor,
      this.config?.interceptorHooks?.responseInterceptorCatch
    )
  }
}

export default Request

3. index.ts 主要是創建 Request 實例

/**
 * 創建實例,可以多個,當你需要請求多個不同功能變數名稱的介面時
 */
import Request from './request'
import { getToken } from '@/utils/auth'

const defRequest = new Request({
  // 這裡用 Easy Mock 模擬了真實介面
  baseURL: 'https://mock.mengxuegu.com/mock/65421527a6dde808a695e96d/official/',
  timeout: 5000,
  showLoading: true,
  interceptorHooks: {
    requestInterceptor: (config) => {
      const token = getToken()
      if (token) {
        config.headers.Authorization = token
      }
      return config
    },
    requestInterceptorCatch: (err) => {
      return err
    },
    responseInterceptor: (res) => {
      return res.data
    },
    responseInterceptorCatch: (err) => {
      return Promise.reject(err)
    }
  }
})

// 創建其他示例,然後導出
// const otherRequest = new Request({...})

export { defRequest }

使用

src 目錄下新建 api 文件夾,並新建 login.ts

1. login.ts

import { defRequest } from '../utils/request'

export const loginApi = (params: any) => {
  // 設置 showLoading,timeout 會覆蓋index.ts里的預設值
  return defRequest.post<any>('/login', params, { showLoading: false, timeout: 1000 })
}

2. 修改 login.vue

<script setup lang="ts">
import { ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useUserStore } from '@store/user'
import { loginApi } from '@/api/login'

defineOptions({
  name: 'V-login'
})

const userStore = useUserStore()
const { userInfo, token } = storeToRefs(userStore)
let userName = ref(userInfo.value.name)
let userToken = ref(token)

const updateUserName = () => {
  userStore.setUserInfo({
    name: userName.value
  })
}
const updateUserToken = () => {
  userStore.setToken(userToken.value)
}

const login = () => {
  loginApi({
    name: userName.value
  })
    .then((res) => {
      userName.value = res.name
      userToken.value = res.token
      updateUserToken()
    })
    .catch((err) => {
      console.log(err)
    })
}
</script>

<template>
  <div>login page</div>
  name:
  <input type="text" v-model="userName" @input="updateUserName" />
  <br />
  token:
  <input type="text" v-model="userToken" />
  <hr />
  <button @click="login">login</button>
</template>

<style scoped></style>

點擊 login 按鈕,即可看到請求。
image

說明

對於 axios 的封裝和使用,這裡要說明幾點:

1. 為什麼要使用 InternalAxiosRequestConfig

axios 源碼有修改,攔截器傳入和返回的參數不再是 AxiosRequestConfig,而是這個新類型 InternalAxiosRequestConfig
想要具體瞭解,可以查看這篇博文 https://blog.csdn.net/huangfengnt/article/details/131490913

2. Request 里的 config 參數

constructor 里的 this.config 會接受所有實例參數,所以通用實例攔截里使用的是 this.config?.xxx
通用攔截里使用的是 config.showLoading,而不是 this.config.showLoading,是為了我們在實際的 api/login.ts 里可以再傳入 showLoading,以滿足我們單個請求的要求。而通過 this.config 里獲取的配置是 request/index.ts 里傳入的配置。在 config.showLoading 之前我們可以列印下這兩個 configconsole.log(this.config, config) 結果如下:
image

如果在 login.ts 里不傳入 showLoading,那麼 config.showLoading 會去拿通用實例 request/index.ts 里的 showLoading
** 當然如果不需要全局載入動畫,整個 loading 也都可以去掉 **

3. 總結下 request/index.tsapi/login.ts 里的參數有什麼不同

request/index.ts 里可以建多個實例,一般以 baseURL 來判斷是否要多個,它的參數是當前url下的通用參數,攔截規則也是;
api/login.ts 是具體的請求,它的大部分參數是url和請求傳參。同一個 baseURL 下有的請求有特殊的要求,那你就可以去加一些參數。
總的來說,request/index.ts 是對 baseURL 一樣的請求的封裝,request/request.ts 是對所有請求的封裝

4. 優化

  • 因為 Easy Mock 的介面支持跨域,所以沒有配到代理里去,如果是正常開發介面,還需要修改 vite.config.ts 里的 proxy。不過我們之前的教程里已有代理配置說明,這裡便不再贅述
  • baseURL 還可以放在 env 變數里,以便區分開發環境和生產環境
  • ** 刪除 loading,這裡只是為了提供一種思路
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Flink中的處理函數(ProcessFunction和KeyedProcessFunction)在對於數據進行顆粒化的精確計算時使用較多,處理函數提供了一個定時服務(TimerService),可以向未來註冊一個定時服務, ...
  • 一、按照月分片 使用場景為按照自然月來分片,每個自然月為一個分片,但是一年有12個月,是不是要有12個數據節點才行呢?並不是。例如我現在只有三個分片資料庫,這樣就可以1月在第一個數據分片中,2月在第二個數據分片中,3月在第三個數據分片中,當來到4月的時候,就會重新開始分片,4月在第一個數據分片,5月 ...
  • 1. 基礎知識回顧 1、索引的有序性,索引本身就是有序的 2、InnoDB中間隙鎖的唯一目的是防止其他事務插入間隙。間隙鎖可以共存。一個事務取得的間隙鎖並不會阻止另一個事務取得同一間隙上的間隙鎖。共用和獨占間隔鎖之間沒有區別。它們彼此之間不衝突,並且執行相同的功能。 3、MySQL預設隔離級別是 R ...
  • 芋道源碼相信很多朋友都很瞭解了,今天我們試著基於FastGPT實現芋道框架的代碼生成。芋道的代碼生成,是基於資料庫表欄位實現的,那我們的思路就是看看如何使用GPT幫我們生成資料庫表結構,只要資料庫表欄位有了,代碼也就生成好了。實現這個需求我們就需要用到FastGPT的高級編排功能。編排的整體思路如下 ...
  • 在之前三期的實時湖倉系列文章中,我們從業務側、產品側、應用側等幾個方向,為大家介紹了實時湖倉方方面面的內容,包括實時湖倉對於企業數字化佈局的重要性以及如何進行實時湖倉的落地實踐等。 本文將從純技術的角度,為大家解析實時湖倉的存儲原理以及生態選型,為企業建設實時湖倉給出技術方面的參考意見。 實時湖倉能 ...
  • 關於GreatSQL字元集的總結 前言 最近的SQL優化工作中經常遇到因字元集或校驗規則不一致導致索引使用不了的問題,修改表的字元集或校驗規則相當於把表重構,表中數據量大時,處理起來費時費力,希望應用開發者在設計之初時註意到此問題,讓後期接手運維的小伙伴少一些負擔。GreatSQL的字元集和校驗規則 ...
  • create database step2; go use step2; go ​ -- 學生表 create table StudentInfo ( stuId char(10) primary key, -- 主鍵 stuName varchar(20), -- 姓名 ClassId int, ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1、背景 與後端對介面時,看到有一個get請求的介面,它的參數是放在body中的 ******get請求參數可以放在body中?? 隨即問了後端,後端大哥說在postman上是可以的,還給我看了截圖 可我傳參怎麼也調不通! 下麵就來探究到 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...