前端的動態數據交互離不開服務端提供的接口,在一個前后端分離的中后台項目中,接口的請求和響應是必不可少的。
那么在架構一個中后台系統的時候,我們如何有效的管理和封裝接口,提高項目接口調用的統一性、可維護性,以及在后端接口還沒有開發完成,在僅有契約的基礎上我們如何有效的模擬接口的調用呢?
接下來便會對以上問題提供個人解決方案供大家參考。
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 就不使用接口域名的功能,我們在正常調用接口的時候便能直接獲取自己模擬的數據結果。