一、Axios 是什么?
Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
特性
- 从浏览器中创建
XMLHttpRequests - 从
node.js创建http请求 - 支持
PromiseAPI - 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换
JSON数据 - 客户端支持防御
XSRF
基本使用
安装
npm install axios
bower install axios
yarn add axios
// 使用 unpkg CDN
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
// 使用 jsDelivr CDN
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
导入
import axios from 'axios'
发送请求
axios({
url:'xxx', // 设置请求的地址
method:"GET", // 设置请求方法
params:{ // get请求使用params进行参数凭借,如果是post请求用data
type: '',
page: 1
}
}).then(res => {
// res为后端返回的数据
console.log(res);
})
用例
发起一个 GET请求
// 向给定ID的用户发起请求
axios.get('/user?ID=12345')
.then(function (response) {
// 处理成功情况
console.log(response);
})
.catch(function (error) {
// 处理错误情况
console.log(error);
})
.then(function () {
// 总是会执行
});
// 上述请求也可以按以下方式完成(可选)
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// 总是会执行
});
// 支持async/await用法
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
发起一个 POST 请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
发起多个并发请求
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
// .then((results) => { // 写法1
// const acct = results[0];
// const perm = results[1];
// })
.then(axios.spread((acct, perm) => { })); // 写法2
写法2中,axios.spread是接收一个函数作为参数,返回一个新的函数。接收的参数函数的参数是axios.all方法中每个请求返回的响应。
二、为什么要封装
axios 的 API 很友好,你完全可以很轻松地在项目中直接使用。
不过随着项目规模增大,如果每发起一次HTTP请求,就要把这些比如设置超时时间、设置请求头、根据项目环境判断使用哪个请求地址、错误处理等等操作,都需要写一遍,这种重复劳动不仅浪费时间,而且让代码变得冗余增加,难以维护。为了提高代码质量,所以应该在项目中二次封装 axios 再使用。
举个例子:
axios('http://localhost:3000/api', {
// 配置代码
method: 'GET',
timeout: 1000,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
Authorization: 'xxx',
},
transformRequest: [function (data, headers) {
return data;
}],
// 其他请求配置...
})
.then((data) => {
// todo: 开始coding业务逻辑代码
console.log(data);
}, (err) => {
// 错误处理
if (err.response.status === 401) {
// handle authorization error
}
if (err.response.status === 403) {
// handle server forbidden error
}
// 其他错误处理.....
console.log(err);
});
如果每个页面都发送类似的请求,都要写一堆的配置与错误处理,就显得过于繁琐了。
这时候我们就需要对axios进行二次封装,让使用更为便利
三、如何封装
-
封装的同时,你需要和后端协商好一些约定,请求头,状态码,请求超时时间......
-
设置接口请求前缀:根据开发、测试、预发、生产环境的不同,前缀需要加以区分;
-
请求头 : 来实现一些具体的业务,必须携带一些参数才可以请求(比如:用户信息);
-
状态码: 根据接口返回的不同
status, 来执行不同的业务,这块需要和后端约定好; -
请求方法:根据
get、post等方法进行一个再次封装,使用起来更为方便 -
请求拦截器: 根据请求的请求头设定,来决定哪些请求可以访问
-
响应拦截器: 这块就是根据 后端返回来的状态码判定执行不同业务
设置接口请求前缀
利用node环境变量来作判断,用来区分开发、测试、生产环境
if (process.env.NODE_ENV === 'development') {
axios.defaults.baseURL = 'http://dev.xxx.com'
} else if (process.env.NODE_ENV === 'production') {
axios.defaults.baseURL = 'http://prod.xxx.com'
// 根据自定义node环境变量区分线上测试、预发、正式环境
}
代理配置
在本地调试的时候,vue 项目中还需要在vue.config.js文件中配置devServer实现代理转发,从而实现跨域。
devServer: {
proxy: {
'/proxyApi': {
target: 'http://dev.xxx.com',
changeOrigin: true,
pathRewrite: {
'/proxyApi': ''
}
}
}
}
React 项目中,需要用到 setupProxy.js文件中配置代理。
const { createProxyMiddleware } = require('http-proxy-middleware');
const options = (path, targetUrl) => {
let devOptions = {
target: targetUrl,
changeOrigin: true,
secure: true, // 设置支持https协议的代理
};
// 是否开启本地 mock接口
if (process.env.IS_MOCK === '1') {
devOptions = Object.assign(devOptions, {
target: process.env.MOCK_URL,
pathRewrite: {
[`^${path}`]: `/mock/api/xxx${path}`,
},
});
}
return devOptions;
};
module.exports = (app) => {
if (process.env.NODE_ENV === 'development') {
if (process.env.IS_MOCK === '1') {
app.use(
'/',
createProxyMiddleware(
options('/', process.env.TEST_URL),
),
);
} else {
app.use(
'/api',
createProxyMiddleware(
options('/api', process.env.TEST_URL),
),
);
// ......
}
}
};
设置请求头与超时时间
大部分情况下,请求头基本是固定的, 也可能会需要一些特殊的请求头,这里将固定的请求头作为基础配置。当需要特殊请求头时,将特殊请求头作为参数传入,覆盖基础配置。 新建 http.js文件:
import axios from 'axios';
import qs from 'qs';
const requestList = [];// 请求列表
const CancelToken = axios.CancelToken; // 取消列表
const sources = {}; // 存request的
axios.defaults.timeout = 10000;
axios.defaults.headers.get['Content-Type'] = ContentType.FORM;
axios.defaults.headers.post['Content-Type'] = ContentType.JSON;
axios.defaults.baseURL = process.env.BASE_URL;
axios.defaults.withCredentials = true;
封装请求方法
先引入封装好的方法,在要调用的接口重新封装成一个方法暴露出去
// 配置类型
interface ConfigType {
loading?: boolean; // 是否显示 loading
isPubParam?: boolean; // 是否带公用参数
ContentType?: string;
}
enum ContentType {
JSON = 'application/json;charset=utf-8',
FORM = 'application/x-www-form-urlencoded;charset=utf-8',
}
const request = function (url, params, config, method) {
return new Promise((resolve, reject) => {
axios[method](url, params, Object.assign({}, config))
.then(
(response) => {
resolve(response.data);
},
(err) => {
if (err.Cancel) {
console.log(err);
} else {
reject(err);
}
},
)
.catch((err) => {
reject(err);
});
});
};
export const post = (url, params, config: ConfigType = {}) => {
if (config.isPubParam) params = { ...pubParams, ...params };
params = qs.stringify(params);
return request(url, params, config, 'post');
};
export const get = (url, params, config: ConfigType = {}) => {
if (config.isPubParam) params = { ...pubParams, ...params };
return request(url, { params }, config, 'get');
};
多域名情况
// 不同环境多域名设置,请求拦截中调用下即可
const setBaseUrl = (options) => {
let baseURL = '',
baseURL2 = '';
if (process.env.NODE_ENV === 'production') {
baseURL = process.env. ONLINE_URL;
baseURL2 = process.env.ONLINE_URL_2;
} else {
baseURL = process.env. TEST_URL;
baseURL2 = process.env. TEST_URL_2;
}
// 匹配替换对应接口域名
if (/^\/api\//.test(options.url)) {
options.url = baseURL + options.url;
} else if (/^\/base\/test\//.test(options.url)) {
options.url = baseURL2 + options.url;
}
};
请求拦截器
请求拦截器可以在每个请求里加上token,做了统一处理后维护起来也方便
axios.interceptors.request.use(
(config) => {
const request =
JSON.stringify(config.url) + JSON.stringify(config.data || '');
setBaseUrl(config); // 调用多域名情况函数
// 这里请求之前判断是否存在token,处理单点登录情况
config.cancelToken = new CancelToken((cancel) => {
sources[request] = cancel;
});
if (requestList.includes(request)) {
sources[request]('取消重复请求');
} else {
requestList.push(request);
if (config['loading'])
// Toast.show('加载中......')
}
return config;
},
function (error) {
return Promise.reject(error);
},
);
响应拦截器
响应拦截器可以在接收到响应后先做一层操作,如根据状态码判断登录状态、授权,如果存在多域名接口,也可以在这里做拦截处理来更新当前接口的 BaseURL。
axios.interceptors.response.use(
(response) => {
const request =
JSON.stringify(response.config.url) +
JSON.stringify(response.config.data);
requestList.splice(
requestList.findIndex((item) => item === request),
1,
);
if (requestList.length === 0) {
Toast.clear();
}
if (response.status === 200) {
switch (response.data.returncode) {
case -5:
Toast.show('认证失效,请重新登录!');
break;
case 102:
Toast.show('操作过快,请稍后再试!');
break;
}
} else {
return Promise.reject(response)
}
return response;
},
(error) => {
if (axios.isCancel(error)) {
requestList.length = 0;
Toast.clear();
throw new axios.Cancel('cancel request');
} else {
// 失败情况的不同返回码对相应处理
switch (error.response.status) {
case 500:
Toast.show('网络请求失败!');break;
}
}
return Promise.reject(error);
},
);
应用
把所有接口放入 api 目录下的 index.js 文件中
import { get, post } from './http'
export const getUser = (params = {}) => get('/api/getuser', params)
接下来到页面中调用
import * as API from 'api/index'
API.getUser({ id: 200 }).then(res => {
console.log(res)
})
这就不把 api 做了统一管理,方便之后的维护与扩展了。
总结
- 封装是编程语言中多态之一,简单对
axios封装,就可以体会到它带来的无限魅力。 - 根据项目的需求进行封装,且简单易用,那就是一个好的方案。