下面开始我们的 axios
封装之旅。
1、目录结构
--api
--request // 文件夹,存放封装类的处理逻辑相关
--axiosApi.js // 封装类导出文件
--config.js // 接口 baseURL 的配置文件,开发或者生产环境
--HttpRequest.js // 类逻辑处理封装文件
--errorHandle.js // 全局错误响应处理文件
--urls // 文件夹,存放所有接口请求 URL 路径
--users.js
--log.js
-- ...
--index.js // 封装类引用入口文件
2、引入所需模块
在HttpRequest.js
文件中,首先引入我们需要用到的模块,如下:
import axios from 'axios' // 引入 axios 库
import qs from 'qs' // qs 模块,用来系列化 post 类型的数据
import store from '@/store' // 状态管理,用于设置 token 或者调用自定义的接口方法
import errorHandle from './errorHandle' // 统一的错误响应处理
import { getToken } from '@/utils/auth' // 从 localStorage 中获取 token
3、HttpRequest 类封装
1)定义默认配置
默认配置中,我们可以根据是开发环境
或者生产环境
来定义对应的 baseURL
,默认请求方式是 GET
,header
头信息设置接受的数据类型和请求发送的数据格式是 json
,超时时间设定为10s
,可根据不同接口请求的实际情况重新定义。
class HttpRequest {
// 设置默认值为空方便使用 devServer 代理
constructor (baseURL = '') {
this.defaultConfig = { // 默认配置
baseURL,
method: 'get',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json; charset=UTF-8'
},
timeout: 1000 * 10 // 请求超时时间
},
isErrorHandle: false // 是否开启全局错误响应提示,默认关闭
}
}
2)创建 axios 实例
创建 axios
实例时,会将用户传入的 options
配置与默认配置合并,然后调用定义好的 interceptors
拦截器(下面会讲到),最后将 axios
实例返回。
/**
* 创建 axios 实例
*
* @param {Object} options 用户自定义配置
* @return {Axios} 返回 axios 实例
* @memberof HttpRequest
*/
createAxiosInstance (options) {
const axiosInstance = axios.create()
// 默认配置和用户自定义配置合并
const newOptions = this.mergeOptions(this.defaultConfig, options)
// 调用拦截器
this.interceptors(axiosInstance)
// 返回实例
return axiosInstance(newOptions)
}
/**
* 合并配置
*
* @param {Object} source 原配置项
* @param {Object} target 目标配置项
* @return {Object} 返回新配置
* @memberof HttpRequest
*/
mergeOptions (source, target) {
if (typeof target !== 'object' || target == null) {
return source
}
return Object.assign(source, target)
}
3)拦截器设置
在拦截器中,我们将 axios
实例作为参数引入,然后定义 请求拦截器
和 响应拦截器
。
-
请求拦截器
:- 常规操作,每次请求都会带上
token
作为与后端数据交互的验证依据; - 这边只处理常用的
get
、post
请求,根据配置中传进来的method
属性来动态的执行请求方式(注意这里没有单独的封装get
、post
请求方法); - 传参方式有点区别,
get
请求方式传参使用params: {id: xx}
形式,post
方式则为data: {id: xx}
; - 结合请求头的
Content-Type
属性的不同,来对传参对象序列化(详情看注释),这样后端才能正常接收到传的参数,当为上传文件类型时,需要自己在请求头中设置'Content-Type': 'multipart/form-data;'
。
- 常规操作,每次请求都会带上
-
响应拦截器
:- 拦截器处理比较简单,根据服务器响应的
status
状态码,正常响应时,将响应数据data
返回出去,异常响应时,则将整个response
以失败的形式返回出去。
- 拦截器处理比较简单,根据服务器响应的
/**
* 拦截器
*
* @param {Axios} instance
* @memberof HttpRequest
*/
interceptors (instance) {
// 请求拦截器
instance.interceptors.request.use((config) => {
const { headers, method, params, data } = config
// 每次请求都携带 token
const token = getToken() || ''
token && (headers.Authorization = token)
// 如果 Content-type 类型不为 'multipart/form-data;' (文件上传类型 )
if (!headers['Content-Type'].includes('multipart')) {
// 如果请求方式为 post 方式,设置 Content-type 类型为 'application/x-www-form-urlencoded; charset=UTF-8'
(method === 'post') && (headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8')
// 根据 contentType 转换 data 数据
const contentType = headers['Content-Type']
// Content-type类型 'application/json;',服务器收到的raw body(原始数据) "{name:"nowThen",age:"18"}"(普通字符串)
// Content-type类型 'application/x-www-form-urlencoded;',服务器收到的raw body(原始数据) name=nowThen&age=18
const paramData = (method === 'get') ? params : data
contentType && (config.data = contentType.includes('json') ? JSON.stringify(paramData) : qs.stringify(paramData))
}
return config
}, (error) => {
// 处理响应错误
errorHandle(error)
return Promise.reject(error)
})
// 响应拦截器
instance.interceptors.response.use((response) => {
const { status, data } = response
// 正常响应
if (status === 200 || (status < 300 || status === 304)) {
if (data.code === 401) {
// token 错误或者过期,需要重新登录,并清空 store 和 localstorge 中的 token
store.dispatch('user/toLogin') // 跳转到登录界面
}
// 返回数据
return Promise.resolve(data)
}
return Promise.reject(response)
}, (error) => {
// 处理响应错误
errorHandle(error)
return Promise.reject(error)
})
}
拦截器异常处理
- 如请求超时、网络异常 / 断开、未授权 / 拒绝访问等情况,则调用封装好的全局统一错误响应处理函数
errorHandle
; - 全局统一错误响应提示默认关闭,可通过配置
isErrorHandle
为true
属性来开启。
- 如请求超时、网络异常 / 断开、未授权 / 拒绝访问等情况,则调用封装好的全局统一错误响应处理函数
errorHandle
在文件errorHandle.js
中定义,如下:
import { message } from 'ant-design-vue'
/**
* axios统一错误处理主要针对HTTP状态码错误
* @param {Object} err
*/
function errorHandle (err) {
// 判断服务器响应
if (err.response) {
switch (err.response.status) {
// 用户无权限访问接口
case 401:
message.error('未授权,请先登录~')
break
case 403:
message.error('服务器拒绝访问~')
break
case 404:
message.error('请求的资源不存在~')
break
case 500:
message.error('服务器异常,请稍后再试~')
break
}
} else if (err.message.includes('timeout')) {
message.error('连接超时~')
} else if (
err.code === 'ECONNABORTED' ||
err.message === 'Network Error' ||
!window.navigator.onLine
) {
message.error('网络已断开,请检查连接~')
} else {
// 进行其他处理
console.log(err.stack)
}
}
export default errorHandle
4)导出 HttpRequest 类
在文件 HttpRequest.js
的末尾将 HttpRequest
类导出:
export default HttpRequest
4、HttpRequest 类使用
1)api 引用入口文件
在 index.js
入口文件中,首先要把封装好的实例引入进来,然后这里使用 webpack
给我们提供的 require.context API
来将定义好的 接口url
自动引入,这样我们就不用一个个手动导入了。
require.context API
使用可以参考我的另一篇文章:自动注册Vue组件【require.context】
import api from './request/axiosApi' // 引入axios封装实例
// https://webpack.js.org/guides/dependency-management/#requirecontext
const apiFiles = require.context('./urls', true, /\.js$/)
// 自动加载 urls 目录下的所有配置的接口
const apiRequest = apiFiles.keys().reduce((apis, apiPath) => {
const name = apiPath.replace(/^\.\/(.*)\.\w+$/, '$1')
const value = apiFiles(apiPath)
apis[name] = Object.keys(value.default).reduce((prev, cur) => {
prev[cur] = (options = {}) => api.createAxiosInstance({ ...value.default[cur], ...options })
return prev
}, {})
return apis
}, {})
// console.log(apiRequest)
export default apiRequest
当然,以上虽然实现了自动化引入,但是却引入了整个接口对象,而有时候我们需要 按需引入
,则可以这样做:
import api from './request/axiosApi' // 引入axios封装实例
import users from './urls/users' // 引入 users api 接口配置
const createApi = (apiUrls) => {
return Object.keys(apiUrls).reduce((prev, cur) => {
prev[cur] = (options = {}) => axios.createAxiosInstance({ ...apiUrls[cur], ...options })
return prev
}, {})
}
export const user = createApi(users)
// 其他接口...
2)接口 url 文件
以 users.js
为例:
export default {
login: {
method: 'post',
url: 'user/login'
},
logout: {
method: 'post',
url: 'user/logout'
},
getInfo: {
method: 'get',
url: 'user/getInfo'
},
...
}
3)axios 实例导出文件
axiosApi.js
:
import config from './config'
import HttpRequest from './HttpRequest'
// 根据当前环境获取API URL根路径
const baseURL = process.env.NODE_ENV === 'production' ? config.baseURL.prod : config.baseURL.dev
// 创建一个HtpRequest对象实例
const axios = new HttpRequest(baseURL)
export default axios
4)api 使用
- 全局引入
在项目 main.js
入口文件中:
import apiRequest from './api' // 引入封装的接口对象
Vue.prototype.$api = apiRequest
在组件中使用,接口请求示例:
this.$api.user.getInfo({
params: {id: xx}
})
.then((res) => {
// 数据处理...
})
.catch((error) => {
// 异常处理...
})
- 按需引入
在使用的地方,接口请求示例:
import { user } from './api' // 按需引入封装的接口对象
user.getInfo({
params: {id: xx}
})
.then((res) => {
// 数据处理...
})
.catch((error) => {
// 异常处理...
})
5、小结
好了,把压箱底的 axios
封装拿出来稍微整理下,于是就产生了这篇文章,对于不同的项目业务需求,可能还有需要改进的地方,大家要是有更好的封装和想法,欢迎在下方导论提出优化建议,感谢你能耐心看到这里。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!