前言
axios 是一个基于 Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范。本章我们将介绍并演示在项目使用 axios 并以项目级编码的要求对 axios 进行二次封装以提升开发效率。
axios 的使用
我们先在当前项目根目录执行
npm install axios
安装axios
,安装成功后可以看到它被添加在package.json
的依赖列表中:
现在我们在
App.vue
的script
块引入axios
并在created
钩子中通过Axios.request(config)
的方式使用:
import Axios from 'axios';
export default {
created() {
Axios.request({
url: 'https://console-mock.apipost.cn/app/mock/project/985272cc-2b5e-4570-d80d-99fff239bc44/todos',
method: 'get'
}).then(rs => {
console.log('响应结果:', rs)
})
}
}
我们调用 axios 提供的
request
方法,并在配置对象config
传入我们请求的地址url
(这里的地址访问的是我自己的mockServer)以及请求的方式method
。因为 axios 是支持 promise 的,所以我们可以直接在then
中获取成功响应的结果:
这里,我们把获取到的响应结果的每个属性进行简要说明:
-
config
:axios
请求的配置信息。 -
data
:由服务器提供的响内容。 -
headers
:服务器的响应头。 -
request
:生成此响应的请求,若请求来自浏览器则是XMLHttpRequest
实例。 -
status
:来自服务器响应的HTTP状态码。 -
statusText
:来自服务器响应的状态信息。
当响应成功时,会执行
then
中的回调,若then
中出现异常错误会被 axios 捕获并执行catch
:
Axios.request({
url: 'https://console-mock.apipost.cn/app/mock/project/985272cc-2b5e-4570-d80d-99fff239bc44/todos',
method: 'get'
}).then(rs => {
console.log('响应结果:', rs)
throw new Error('代码异常')
}).catch(err => {
console.log('出现异常:', err)
})
除了这种情况,当请求超时、取消以及状态码
>300
都会触发catch
。这里我们用到了
url
和method
作为用于发出请求的配置参数,除此之外还有以下常用配置项,其中url
是必传的,如果未指定method
,则请求将默认为GET
:
{
url: '/user', // 请求地址
method: 'get', // 请求方式,默认get
baseURL: 'https://some-domain.com/api/', // `除非url是绝对路径,否则baseURL将被置于url之前
headers: {'X-Requested-With': 'XMLHttpRequest'}, // 自定义请求头
params: {
ID: 12345
}, // 携带在URL上的查询字符串
data: {
firstName: 'Fred'
}, // 请求体的数据
timeout: 1000, // 指定请求超时前的毫秒数
responseType: 'json', // 表示服务器响应的数据类型,默认为json
withCredentials: false // 跨域请求时是否携带上cookie
}
除了使用
request
方法,axios 为方便起见,为所有支持的请求方法提供了别名:get、post、put、delete、options、patch、head
。我们以get
为例,此类的方法接收的第一个参数为请求地址,第二参数为指定的配置,该配置会与 axios 实例的配置合并:
created() {
const url = 'https://console-mock.apipost.cn/app/mock/project/985272cc-2b5e-4570-d80d-99fff239bc44/todos'
Axios.request({
url,
method: 'get'
}).then(rs => {
console.log('Axios.request响应结果:', rs)
})
Axios.get(url,{
params: {
id: 1001,
type: 2
}
}).then(rs => {
console.log('Axios.get响应结果:', rs)
})
}
实际上我们会有一些通用的接口行为,如loading状态、错误处理、会话保持等,显然我们希望能在一个地方通用配置好这些行为避免在业务处一一实现,这时候你就需要 axios 提供的拦截器:
<script>
import Axios from 'axios';
// 添加请求拦截器
Axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.log('---请求拦截---')
config.headers.token = 'auth-08jh34'
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
Axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.log('---响应拦截---')
if (response.status === 200) {
response = response.data
}
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default {
created() {
const url = 'https://console-mock.apipost.cn/app/mock/project/985272cc-2b5e-4570-d80d-99fff239bc44/todos'
Axios.get(url,{
params: {
id: 1001,
type: 2
}
}).then(rs => {
console.log('Axios.get响应结果:', rs)
})
}
}
</script>
我们对每次请求发送前都添加了一个自定义的
token
头。接收响应时,若状态码为200
则直接输出服务器响应的data
:
axios 的封装
在具体的视图中应该更专注于业务的处理,对于涉及数据源的操作(获取和更改)应该单独在一个地方被统一管理并暴露使用方法,若引入使用了第三方状态管理器(如vuex、redux),视图应使用管理器操作以保持数据流是单向的。现在我们仅就视图不使用第三方状态管理器的情况进行 axios 的封装和使用演示。
我们在
src
目录下新建utils
目录用于存放我们的工具类,并在该目录中创建我们用于封装 axios 的request.js
文件:
先定义
IS_PRPDUCTION
常量用于区分环境,这里暂定只区分生产环境和开放环境。通过axios的create
方法创建一个实例并设置好基础URL和请求超时时间并导出该实例:
import axios from 'axios';
// 是否是生产环境
const IS_PRPDUCTION = false;
const baseURL = IS_PRPDUCTION ?
'https://console-mock.apipost.cn/app/mock/project/985272cc-2b5e-4570-d80d-99fff239bc44' :
'http://test-environment/api';
const $http = axios.create({
baseURL,
timeout: 60000,
});
// TODO
export default $http;
现在我们先将请求拦截处理、响应拦截处理、请求错误处理、响应错误处理的方法先定义出来:
......
// 请求拦截处理
const requestInterceptor = config => {
return config;
}
// 请求错误处理
const requestErrorHandler = error => {
return Promise.reject(error);
}
// 响应拦截处理
const responseInterceptor = response => {
return response;
}
// 响应错误处理
const responseErrorHandler = error => {
return Promise.reject(error);
}
$http.interceptors.request.use(requestInterceptor, requestErrorHandler);
$http.interceptors.response.use(responseInterceptor, responseErrorHandler);
export default $http;
首先统一添加自定义请求头
auth-token
用于会话状态验证,并对状态码200
的响应进行“去皮”操作直接返回服务端的数据:
// 请求拦截处理
const requestInterceptor = config => {
// 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
const token = localStorage.getItem('auth-token');
token && (config.headers['auth-token'] = token);
return config;
}
......
// 响应拦截处理
const responseInterceptor = response => {
const { status, data } = response
if(status === 200) return data
return response
}
现在我们模拟一个登录接口和获取列表数据接口,完善 token 使用的整个流程。在 src 目录下新建
api
目录用于存放我们所有的接口,并在该目录下的user.js
中定义接口:
import $http from '../utils/request'
export const login = () => {
return $http.post('/user/login')
}
export const getTodos = () => {
return $http.get('/user/todos')
}
在 APP.vue 添加相应业务代码:
<template>
<div id="app">
<button @click="getTodos">获取数据</button>
<ul>
<li v-for="(item, index) in todos" :key="item.id">
<input type="checkbox" id="checkbox" v-model="item.done">
<p>{{ index + 1 }}、</p>
<p>{{ item.content }}</p>
</li>
</ul>
</div>
</template>
<script>
import { login, getTodos } from './api/user'
export default {
data() {
return {
todos: []
}
},
methods: {
getTodos() {
getTodos().then(res => {
const { code, data } = res
if(code === 0) {
this.todos = data.list
} else {
// 这里的code是业务状态码,根据业务需求进行相应提示操作
}
})
}
},
beforeCreate() {
login().then(res => {
const { code, data } = res
if(code === 0) {
localStorage.setItem('auth-token', data.token)
alert('登录成功')
} else {
// 这里的code是业务状态码,根据业务需求进行相应提示操作
}
})
}
}
</script>
现在我们查看控制台,发现请求接口
/user/todos
时请求头带上了通过登录拿到的token:
当我们会话失效即超过 toekn 有效期时,通常会将用户重定向到登录页进行重新登录,这时候就需要在响应拦截器的错误处理里面添加以下类似代码:
// 响应错误处理
const responseErrorHandler = error => {
if(error.response){
// 失败响应的status需要在response中获得
switch(error.response.status){
// 对得到的状态码的处理,具体的设置视自己的情况而定
case 401:
case 404:
console.log('token失效和404')
// 通常结合 vue-router 进行路由控制
window.location.href='/'
break
case 405:
console.log('不支持的方法')
break
// case ...
default:
console.log('其他错误')
break
}
}
return Promise.reject(error)
}
网络请求时进行相应的 loading 状态展示是必不可少的,现在我们也将它进行统一处理:
......
let ExistingRequest = 0
const showLoading = () => {
ExistingRequest++;
// 结合全局状态和UI库进行loading展示
// 类似触发 -> $bus.emit('showLoading');
};
const hideLoading = () => {
ExistingRequest--;
if (ExistingRequest <= 0) {
// 结合全局状态和UI库进行loading隐藏
// 类似触发 -> $bus.emit('hideLoading');
}
};
// 请求拦截处理
const requestInterceptor = config => {
// 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
const token = localStorage.getItem('auth-token');
console.log('auth-token:', token)
token && (config.headers['auth-token'] = token);
showLoading()
return config
}
// 请求错误处理
const requestErrorHandler = error => {
return Promise.reject(error);
}
// 响应拦截处理
const responseInterceptor = response => {
const { status, data } = response
if(status === 200) return data
hideLoading()
return response
}
// 响应错误处理
const responseErrorHandler = error => {
hideLoading()
if(error.response){
// 失败响应的status需要在response中获得
switch(error.response.status){
// 对得到的状态码的处理,具体的设置视自己的情况而定
case 401:
case 404:
console.log('token失效和404')
// 或使用vue-router进行控制
window.location.href='/'
break
case 405:
console.log('不支持的方法')
break
// case ...
default:
console.log('其他错误')
break
}
}
// 注意这里应该return promise.reject(),
// 因为如果直接return err则在调用此实例时,响应失败了也会进入then(res=>{})而不是reject或catch方法
return Promise.reject(error)
}
内容预告
之前我们都是在 App.vue 单组件中进行演示代码的,在下一章我们将继续深入组件,学习复合组件的相关知识和运用实践。