接口封裝


前端的動態數據交互離不開服務端提供的接口,在一個前后端分離的中后台項目中,接口的請求和響應是必不可少的。

那么在架構一個中后台系統的時候,我們如何有效的管理和封裝接口,提高項目接口調用的統一性、可維護性,以及在后端接口還沒有開發完成,在僅有契約的基礎上我們如何有效的模擬接口的調用呢?

接下來便會對以上問題提供個人解決方案供大家參考。

1. 不封裝存在的問題

首先談談接口封裝,因為我們使用的請求庫是 axios,所以接下來的示例都以 axios 來舉例。

那么在沒有封裝接口的項目中,你可能隨處可見接口的直接調用方法,比如像這樣:

axios.post('/user', {
    firstName: 'zhang',
    lastName: 'san'
  })
  .then(function (response) {
    console.log(response);
  });
...

axios.get('/user?ID=12345')
    .then(function (response) {
    // handle success
    console.log(response);
  });
復制代碼

 

這樣的寫法會存在一些缺點,主要有以下幾點:

  • 接口 url 沒有統一管理,散落在項目的各個地方
  • 如果需要在接口調用成功和失敗時做一些處理,需要在每個地方進行添加
  • 特殊請求頭以及取消請求方法需要單獨進行編寫

2. 修改默認配置

既然會存在上述問題,那么我們就需要去解決。在之前介紹的項目目錄結構中,我們會發現有 services 文件夾,這就是用來存放封裝的接口和調用的方法的。

在接口封裝過程中,首先我們需要修改 axios 的默認配置,如下:

import axios from 'axios'

// 修改默認配置
axios.defaults.headers.post['Content-Type'] = 'application/json'
axios.defaults.headers.get['Content-Type'] = 'application/json'
axios.defaults.withCredentials = true // 表示是否跨域訪問請求
復制代碼

 

可以把你常用的請求頭的 Content-Type 設置為默認值,同時開啟跨域功能。

3. 設置攔截器

接下來需要編寫下請求和響應的攔截器,來對請求和響應進行適時攔截,比如再調用重復接口時,取消上一次未完成的相同請求:

const CancelToken = axios.CancelToken
const httpPending = [] // 用於存儲每個ajax請求的取消函數和ajax標識

// 取消請求方法
const cancelHttp = (name, config = {}) => {
    httpPending.forEach((e, i) => {
        if (e.n === name || e.n === config.xhrName) { // 當前請求在數組中存在時執行函數體
            e.f() // 執行取消操作
            httpPending.splice(i, 1) // 把這條記錄從數組中移除
        }
    })
}

// 請求攔截器
axios.interceptors.request.use(config => {
    // 取消上一次未完成的相同請求,注意項目中是否存在風險
    cancelHttp(null, config)

    config.cancelToken = new CancelToken(c => {
        if (config.xhrName) {
            httpPending.push({
                n: config.xhrName,
                u: `${config.url}&${config.method}`,
                f: c
            })
        }
    })

    return config
}, error => Promise.reject(error))

// 響應攔截器
axios.interceptors.response.use(res => {
    cancelHttp(null, res.config) // 響應成功把已經完成的請求從 httpPending 中移除

    checkStatus(res) // 校驗響應狀態
    const response = res.data

    return Promise.resolve(response)
}, error => Promise.reject(error))
復制代碼

 

上述兩個攔截器主要做了重復請求的攔截功能,在請求頭中將請求的取消請求方法和標識符號插入數組中,當然之前需要去數組中查找是否存在相同請求,存在則提前取消請求,最后在響應時把已經完成的請求信息從數組中移除。

這里為了避免風險,我們需要在接口調用的地方手動傳入一個 xhrName 標識這個請求名稱才會取消重復調用該接口的請求,其余接口不做處理。

同時我們也將 cancelHttp 暴露給全局,滿足手動取消請求的需要:

Vue.prototype.$cancelHttp = cancelHttp
復制代碼

 

4. 暴露調用方法

當我們完成了針對 axios 的一些設置后,我們最終的目的是使用它來請求和處理接口,那么是時候對請求調用的方法進行封裝和暴露了:

import uriConfig from '@/config/apiUriConf'
import GLOBAL from '@/config/global' // 全局變量

...

export default class Http {
    static async request(method, url, opts, type) {
        // 開啟本地 mock 的話,不使用接口域名
        let hostName = GLOBAL.mockLocal ? '' : uriConfig.apiUrl
        
        // 特殊接口域名
        let otherName = GLOBAL.mockLocal ? '' : (uriConfig[type] || '')
        
        // type 存在則使用對應的接口,否則使用通用接口
        let uri = type ? `${otherName}${url}` : `${hostName}${url}`
        
        // 接口別名、請求方式及url
        let params = {
            xhrName: (opts && opts.name) || '',
            method,
            url: uri,
        }
        
        // 請求數據
        params.data = opts.body || {}
        
        // 設置特殊請求頭
        if (opts.type === 'formData') {
            params.headers = {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }

        return axios(params)
    }

    static get(url, opts) {
        return this.request('GET', url, opts)
    }

    static put(url, opts) {
        return this.request('PUT', url, opts)
    }

    static post(url, opts) {
        return this.request('POST', url, opts)
    }

    static patch(url, opts) {
        return this.request('PATCH', url, opts)
    }

    static delete(url, opts) {
        return this.request('DELETE', url, opts)
    }
}
復制代碼

 

上方我們將封裝了一個 Http 類,其中包含了 get、post 等請求方法,這些請求方法內部都會去調用 request 方法,該方法會通過傳入的不同參數執行原始 axios 的請求調用,返回一個 Promise。

5. 引用調用方法

那么哪里去使用這個 Http 類呢,我們可以在 services 文件夾中再建立其他接口管理文件,比如 user.js,用於存放用戶相關的接口:

// user.js
import Http from './http'

// 獲取用戶信息
export const getUserInfo = params => Http.post('/getUserInfo', {
    body: params
})

 

 復制代碼

最后在調用的地方引入 getUserInfo 方法,傳入對應的參數對象即可。

import { getUserInfo } from '@services/user'

getUserInfo({
    token: 'xxx'
})
復制代碼

 

如此我們便完成了接口封裝的基本功能

 

接口模擬

剛剛在封裝接口的時候,我們看到了 GLOBAL.mockLocal 這一全局變量,用於判斷是否開啟和關閉接口的 mock。

首先什么是接口 mock?意思就是模仿接口返回的數據。那么為什么要模仿呢?因為在接口還沒開發完的場景下,前端在得知接口文檔和格式后想本地模擬數據請求的功能,這時候開啟接口 mock 就會變得十分簡單。

這里我們通常會用一個比較實用的庫 mockjs 來實現我們的功能,用法很簡單,在我們事先創建的 mock 文件夾下創建 index.js 文件:

import GLOBAL from '@/config/global'

// 全局變量
if (GLOBAL.mockLocal) {
    let Mock = require('mockjs')

    // 用戶信息接口
    Mock.mock('/getUserInfo', () => ({
        result: {
            name: '張三',
            sex: 'man',
            age: 12,
        },
        status: true, // 數據狀態
        statusCode: '200', // 狀態碼
        message: '請求成功' // 提示信息
    })
}

 

 復制代碼

文件中我們還是以用戶信息接口為例,當開啟全局 mock 的時候,我們便使用 mockjs 來模擬數據的返回。

當然你還需要的項目的入口文件中引用下:

// main.js
import '@/mock' // 全局變量

 

 復制代碼

之后配合接口封裝時判斷開啟 mock 就不使用接口域名的功能,我們在正常調用接口的時候便能直接獲取自己模擬的數據結果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM