axios详解以及完整封装方法

打印 上一主题 下一主题

主题 924|帖子 924|积分 2772

"""
一、axios是什么

Axios 是一个基于 promise 网络请求库,作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
axios有以下特性:

  • 从浏览器创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持防御XSRF
axios可以请求的方法:

  • get:获取数据,请求指定的信息,返回实体对象
  • post:向指定资源提交数据(例如表单提交或文件上传)
  • put:更新数据,从客户端向服务器传送的数据取代指定的文档的内容
  • patch:更新数据,是对put方法的补充,用来对已知资源进行局部更新
  • delete:请求服务器删除指定的数据
  • head:获取报文首部
请求方法别名
为了方便起见,axios为所有支持的请求方法提供了别名:

  • axios(config)
  • axios.request(config)
  • axios.get(url [,config])
  • axios.post(url [,data [,config]])
  • axios.put(url [,data [,config]])
  • axios.delete(url [,config])
  • axios.patch(url [,data [,config]])
  • axios.head(url [,config])
二.axios实例及配置方法

1.创建axios实例
axios.create([config])
可以同时创建多个axios实例。
示例代码
"""
  1. const instance = axios.create({
  2. baseURL: 'https://some-domain.com/api/',
  3. timeout: 1000,
  4. headers: {'X-Custom-Header': 'foobar'}
  5. });
复制代码
"""
实例方法
以下是可用的实例方法。指定的配置将与实例的配置合并。

  • axios#request(config)
  • axios#get(url[, config])
  • axios#delete(url[, config])
  • axios#head(url[, config])
  • axios#options(url[, config])
  • axios#post(url[, data[, config]])
  • axios#put(url[, data[, config]])
  • axios#patch(url[, data[, config]])
  • axios#getUri([config])
2.配置方法
配置对象常用的配置项:
这些是创建请求时可以用的配置选项。只有 url 是必需的。如果没有指定 method,请求将默认使用 GET 方法。更多配置项请查看官方文档
"""
  1. {
  2.   // 路径url
  3.   url: '/user',
  4.   // 请求方法,默认get
  5.   method: 'get',
  6.   //基础url,最终请求的url是 baseURL+url拼接,所以再全局设置默认,可以使得发送请求时的url变得简洁
  7.   baseURL: 'https://some-domain.com/api/',
  8.   //设置请求头
  9.   headers: {'X-Requested-With': 'XMLHttpRequest'},
  10.   //设置请求url的query参数,可以使得url简洁。
  11.   //比如url是https://some-domain.com/api/user  然后params如下设置,那么最终的url是:
  12.   //https://some-domain.com/api/user?ID=12345&name=Jack
  13.   params: {
  14.         ID: 12345,
  15.         name:"Jack"
  16.   },
  17. //设置请求体
  18.   data: {
  19.         firstName: 'Fred'
  20.   },
  21.   //设置请求的另外一种格式,不过这个是直接设置字符串的
  22.   data: 'Country=Brasil&City=Belo Horizonte',
  23. //请求超时,单位毫秒,默认0,不超时。
  24.   timeout: 1000,
  25.   //响应数据类型,默认json
  26.   responseType: 'json',
  27.   //响应数据的编码规则,默认utf-8
  28.   responseEncoding: 'utf8',
  29.         //响应体的最大长度
  30.   maxContentLength: 2000,
  31.   // 请求体的最大长度
  32.   maxBodyLength: 2000,
  33.   //设置响应状态码为多少时是成功,调用resolve,否则调用reject失败
  34.   //默认是大于等于200,小于300
  35.   validateStatus: function (status) {
  36.         return status >= 200 && status < 300;
  37.   },
复制代码
"""
默认配置
可以设置全局默认配置,是为了避免多种重复配置在不同请求中重复,比如baseURL、timeout等,这里设置baseURL。
全局 axios 默认值
"""
  1. axios.defaults.baseURL = 'https://api.example.com';
  2. axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
  3. axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
复制代码
"""
自定义实例默认值
"""
  1. // 创建实例时配置默认值
  2. const instance = axios.create({
  3.   baseURL: 'https://api.example.com'
  4. });
  5. // 创建实例后修改默认值
  6. instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
复制代码
""""
配置的优先级
配置将会按优先级进行合并。它的顺序是:在 lib/defaults.js 中找到的库默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。后面的优先级要高于前面的。
三、拦截器

在请求或响应被 then 或 catch 处理前拦截它们,自定义的axios实例也可添加拦截器,如:
"""
  1. const instance = axios.create();
  2. instance.interceptors.request.use(function () {/*...*/});
复制代码
"""
请求拦截器
示例代码:
"""
  1. // 添加请求拦截器
  2. axios.interceptors.request.use(function (config) {
  3.         // 在发送请求之前做些什么
  4.         return config;
  5.   }, function (error) {
  6.         // 对请求错误做些什么
  7.         return Promise.reject(error);
  8.   });
复制代码
"""
响应拦截器
示例代码:
"""
  1. // 添加响应拦截器
  2. axios.interceptors.response.use(function (response) {
  3.         // 2xx 范围内的状态码都会触发该函数。
  4.         // 对响应数据做点什么
  5.         return response;
  6.   }, function (error) {
  7.         // 超出 2xx 范围的状态码都会触发该函数。
  8.         // 对响应错误做点什么
  9.         return Promise.reject(error);
  10.   });
复制代码
"""
取消拦截器
示例代码:
"""
  1. const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
  2. axios.interceptors.request.eject(myInterceptor);
复制代码
"""
四、取消请求

注意:从 v0.22.0 开始,Axios 支持以 fetch API 方式——  AbortController 取消请求,CancelToken API被弃用
这里我们两种方法都介绍一下,使用过程中能用 AbortController 就尽量别用 CancelToken
AbortController
"""
  1. const controller = new AbortController();
  2. axios.get('/foo/bar', {
  3.    signal: controller.signal
  4. }).then(function(response) {
  5.    //...
  6. });
  7. // 取消请求
  8. controller.abort()
复制代码
"""
CancelToken
"""
  1. let source = axios.CancelToken.source();
  2. axios.get('/users/12345',{
  3.                                 cancelToken: source.token
  4.                         }).then(res=>{
  5.                                 console.log(res)
  6.                         }).catch(err=>{
  7.                                 //取消请求后会执行该方法
  8.                                 console.log(err)
  9.                         })
  10. //取消请求,参数可选,该参数信息会发送到请求的catch中
  11. source.cancel('取消后的信息');
复制代码
"""
也可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建一个 cancel token:
"""
  1. const CancelToken = axios.CancelToken;
  2. let cancel;
  3. axios.get('/user/12345', {
  4.   cancelToken: new CancelToken(function executor(c) {
  5.         // executor 函数接收一个 cancel 函数作为参数
  6.         cancel = c;
  7.   })
  8. });
  9. // 取消请求
  10. cancel();
复制代码
"""
注意: 可以使用同一个 cancel token 或 signal 取消多个请求
五、axios封装

先设计我们想要这个通用请求能达到什么样的效果:

  • 优化配置,设置默认配置项(responseType、跨域携带cookie、token、超时设置)
  • 统一设置请求头
  • 根据环境设置 baseURL
  • 通过 Axios 方法直接发起请求
  • 添加请求拦截器
  • 添加响应拦截器
  • 导出 Promise 对象
  • 封装 Post 方法,精简 post 请求方式
  • 封装 Get 方法,精简 get 请求方式
  • 请求成功,配置业务状态码
  • 全局的loading配置
  1. 一、axios的封装
  2. 在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。如果还对axios不了解的,可以移步axios文档。
  3. 安装
  4. npm install axios; // 安装axios
  5. 引入
  6. 一般我会在项目的src目录中,新建一个request文件夹,然后在里面新建一个http.js和一个api.js文件。http.js文件用来封装我们的axios,api.js用来统一管理我们的接口。
  7. // 在http.js中引入axios
  8. import axios from 'axios'; // 引入axios
  9. import QS from 'qs'; // 引入qs模块,用来序列化post类型的数据,后面会提到
  10. // vant的toast提示框组件,大家可根据自己的ui组件更改。
  11. import { Toast } from 'vant';
  12. 环境的切换
  13. 我们的项目环境可能有开发环境、测试环境和生产环境。我们通过node的环境变量来匹配我们的默认的接口url前缀。axios.defaults.baseURL可以设置axios的默认请求地址就不多说了。
  14. // 环境的切换
  15. if (process.env.NODE_ENV == 'development') {   
  16.         axios.defaults.baseURL = 'https://www.baidu.com';}
  17. else if (process.env.NODE_ENV == 'debug') {   
  18.         axios.defaults.baseURL = 'https://www.ceshi.com';
  19. }
  20. else if (process.env.NODE_ENV == 'production') {   
  21.         axios.defaults.baseURL = 'https://www.production.com';
  22. }
  23. 设置请求超时
  24. 通过axios.defaults.timeout设置默认的请求超时时间。例如超过了10s,就会告知用户当前请求超时,请刷新等。
  25. axios.defaults.timeout = 10000;
  26. post请求头的设置
  27. post请求的时候,我们需要加上一个请求头,所以可以在这里进行一个默认的设置,即设置post的请求头为application/x-www-form-urlencoded;charset=UTF-8
  28. axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
  29. 请求拦截
  30. 我们在发送请求前可以进行一个请求的拦截,为什么要拦截呢,我们拦截请求是用来做什么的呢?比如,有些请求是需要用户登录之后才能访问的,或者post请求的时候,我们需要序列化我们提交的数据。这时候,我们可以在请求被发送之前进行一个拦截,从而进行我们想要的操作。
  31. 请求拦截
  32. // 先导入vuex,因为我们要使用到里面的状态对象
  33. // vuex的路径根据自己的路径去写
  34. import store from '@/store/index';
  35. // 请求拦截器axios.interceptors.request.use(   
  36.         config => {        
  37.                 // 每次发送请求之前判断vuex中是否存在token        
  38.                 // 如果存在,则统一在http请求的header都加上token,这样后台根据token判断你的登录情况
  39.                 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
  40.                 const token = store.state.token;        
  41.                 token && (config.headers.Authorization = token);        
  42.                 return config;   
  43.         },   
  44.         error => {        
  45.                 return Promise.error(error);   
  46. })
  47. 这里说一下token,一般是在登录完成之后,将用户的token通过localStorage或者cookie存在本地,然后用户每次在进入页面的时候(即在main.js中),会首先从本地存储中读取token,如果token存在说明用户已经登陆过,则更新vuex中的token状态。然后,在每次请求接口的时候,都会在请求的header中携带token,后台人员就可以根据你携带的token来判断你的登录是否过期,如果没有携带,则说明没有登录过。这时候或许有些小伙伴会有疑问了,就是每个请求都携带token,那么要是一个页面不需要用户登录就可以访问的怎么办呢?其实,你前端的请求可以携带token,但是后台可以选择不接收啊!
  48. 响应的拦截
  49. // 响应拦截器
  50. axios.interceptors.response.use(   
  51.         response => {   
  52.                 // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据     
  53.                 // 否则的话抛出错误
  54.                 if (response.status === 200) {            
  55.                         return Promise.resolve(response);        
  56.                 } else {            
  57.                         return Promise.reject(response);        
  58.                 }   
  59.         },   
  60.         // 服务器状态码不是2开头的的情况
  61.         // 这里可以跟你们的后台开发人员协商好统一的错误状态码   
  62.         // 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
  63.         // 下面列举几个常见的操作,其他需求可自行扩展
  64.         error => {            
  65.                 if (error.response.status) {            
  66.                         switch (error.response.status) {               
  67.                                 // 401: 未登录
  68.                                 // 未登录则跳转登录页面,并携带当前页面的路径
  69.                                 // 在登录成功后返回当前页面,这一步需要在登录页操作。               
  70.                                 case 401:                    
  71.                                         router.replace({                        
  72.                                                 path: '/login',                        
  73.                                                 query: {
  74.                                                         redirect: router.currentRoute.fullPath
  75.                                                 }
  76.                                         });
  77.                                         break;
  78.             // 403 token过期
  79.             // 登录过期对用户进行提示
  80.             // 清除本地token和清空vuex中token对象
  81.             // 跳转登录页面               
  82.             case 403:
  83.                  Toast({
  84.                     message: '登录过期,请重新登录',
  85.                     duration: 1000,
  86.                     forbidClick: true
  87.                 });
  88.                 // 清除token
  89.                 localStorage.removeItem('token');
  90.                 store.commit('loginSuccess', null);
  91.                 // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
  92.                 setTimeout(() => {                        
  93.                     router.replace({                           
  94.                         path: '/login',                           
  95.                         query: {
  96.                             redirect: router.currentRoute.fullPath
  97.                         }                        
  98.                     });                    
  99.                 }, 1000);                    
  100.                 break;
  101.             // 404请求不存在
  102.             case 404:
  103.                 Toast({
  104.                     message: '网络请求不存在',
  105.                     duration: 1500,
  106.                     forbidClick: true
  107.                 });
  108.                 break;
  109.             // 其他错误,直接抛出错误提示
  110.             default:
  111.                 Toast({
  112.                     message: error.response.data.message,
  113.                     duration: 1500,
  114.                     forbidClick: true
  115.                 });
  116.         }
  117.         return Promise.reject(error.response);
  118.     }
  119. }   
  120. });
  121. 响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对他进行一些处理。例如上面的思想:如果后台返回的状态码是200,则正常返回数据,否则的根据错误的状态码类型进行一些我们需要的错误,其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作。
  122. 要注意的是,上面的Toast()方法,是我引入的vant库中的toast轻提示组件,你根据你的ui库,对应使用你的一个提示组件。
  123. 封装get方法和post方法
  124. 我们常用的ajax请求方法有get、post、put等方法,相信小伙伴都不会陌生。axios对应的也有很多类似的方法,不清楚的可以看下文档。但是为了简化我们的代码,我们还是要对其进行一个简单的封装。下面我们主要封装两个方法:get和post。
  125. get方法:我们通过定义一个get函数,get函数有两个参数,第一个参数表示我们要请求的url地址,第二个参数是我们要携带的请求参数。get函数返回一个promise对象,当axios其请求成功时resolve服务器返回 值,请求失败时reject错误值。最后通过export抛出get函数。
  126. /**
  127. * get方法,对应get请求
  128. * @param {String} url [请求的url地址]
  129. * @param {Object} params [请求时携带的参数]
  130. */
  131. export function get(url, params){   
  132.         return new Promise((resolve, reject) =>{        
  133.                 axios.get(url, {            
  134.                         params: params        
  135.                 }).then(res => {
  136.                         resolve(res.data);
  137.                 }).catch(err =>{
  138.                         reject(err.data)        
  139.         })   
  140. });}
  141. post方法:原理同get基本一样,但是要注意的是,post方法必须要使用对提交从参数对象进行序列化的操作,所以这里我们通过node的qs模块来序列化我们的参数。这个很重要,如果没有序列化操作,后台是拿不到你提交的数据的。这就是文章开头我们import QS from 'qs';的原因。如果不明白序列化是什么意思的,就百度一下吧,答案一大堆。
  142. /**
  143. * post方法,对应post请求
  144. * @param {String} url [请求的url地址]
  145. * @param {Object} params [请求时携带的参数]
  146. */
  147. export function post(url, params) {
  148.         return new Promise((resolve, reject) => {
  149.                  axios.post(url, QS.stringify(params))
  150.                 .then(res => {
  151.                         resolve(res.data);
  152.                 })
  153.                 .catch(err =>{
  154.                         reject(err.data)
  155.                 })
  156.         });
  157. }
  158. 这里有个小细节说下,axios.get()方法和axios.post()在提交数据时参数的书写方式还是有区别的。区别就是,get的第二个参数是一个{},然后这个对象的params属性值是一个参数对象的。而post的第二个参数就是一个参数对象。两者略微的区别要留意哦!
  159. axios的封装基本就完成了,下面再简单说下api的统一管理。
  160. 整齐的api就像电路板一样,即使再复杂也能很清晰整个线路。上面说了,我们会新建一个api.js,然后在这个文件中存放我们所有的api接口。
  161. 首先我们在api.js中引入我们封装的get和post方法
  162. /**   
  163. * api接口统一管理
  164. */
  165. import { get, post } from './http'
  166. 现在,例如我们有这样一个接口,是一个post请求:
  167. http://www.baiodu.com/api/v1/users/my_address/address_edit_before
  168. 我们可以在api.js中这样封装:
  169. export const apiAddress = p => post('api/v1/users/my_address/address_edit_before', p);
  170. 我们定义了一个apiAddress方法,这个方法有一个参数p,p是我们请求接口时携带的参数对象。而后调用了我们封装的post方法,post方法的第一个参数是我们的接口地址,第二个参数是apiAddress的p参数,即请求接口时携带的参数对象。最后通过export导出apiAddress。
  171. 然后在我们的页面中可以这样调用我们的api接口:
  172. import { apiAddress } from '@/request/api';// 导入我们的api接口
  173. export default {        
  174.         name: 'Address',   
  175.         created () {
  176.                 this.onLoad();
  177.         },
  178.         methods: {            
  179.                 // 获取数据            
  180.                 onLoad() {
  181.                         // 调用api接口,并且提供了两个参数               
  182.                         apiAddress({                    
  183.                                 type: 0,                    
  184.                                 sort: 1               
  185.                         }).then(res => {
  186.                                 // 获取数据成功后的其他操作
  187.                                 ………………               
  188.                         })            
  189.                 }        
  190.         }
  191. }
  192. 其他的api接口,就在pai.js中继续往下面扩展就可以了。友情提示,为每个接口写好注释哦!!!
  193. api接口管理的一个好处就是,我们把api统一集中起来,如果后期需要修改接口,我们就直接在api.js中找到对应的修改就好了,而不用去每一个页面查找我们的接口然后再修改会很麻烦。关键是,万一修改的量比较大,就规格gg了。还有就是如果直接在我们的业务代码修改接口,一不小心还容易动到我们的业务代码造成不必要的麻烦。
  194. 好了,最后把完成的axios封装代码奉上。
  195. /**axios封装
  196. * 请求拦截、相应拦截、错误统一处理
  197. */
  198. import axios from 'axios';import QS from 'qs';
  199. import { Toast } from 'vant';
  200. import store from '../store/index'
  201. // 环境的切换
  202. if (process.env.NODE_ENV == 'development') {   
  203.         axios.defaults.baseURL = '/api';
  204. } else if (process.env.NODE_ENV == 'debug') {   
  205.         axios.defaults.baseURL = '';
  206. } else if (process.env.NODE_ENV == 'production') {   
  207.         axios.defaults.baseURL = 'http://api.123dailu.com/';
  208. }
  209. // 请求超时时间
  210. axios.defaults.timeout = 10000;
  211. // post请求头
  212. axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
  213. // 请求拦截器
  214. axios.interceptors.request.use(   
  215.         config => {
  216.                 // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
  217.                 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断
  218.                 const token = store.state.token;        
  219.                 token && (config.headers.Authorization = token);        
  220.                 return config;   
  221.         },   
  222.         error => {        
  223.                 return Promise.error(error);   
  224.         })
  225. // 响应拦截器
  226. axios.interceptors.response.use(   
  227.         response => {        
  228.                 if (response.status === 200) {            
  229.                         return Promise.resolve(response);        
  230.                 } else {            
  231.                         return Promise.reject(response);        
  232.                 }   
  233.         },
  234.         // 服务器状态码不是200的情况   
  235.         error => {        
  236.                 if (error.response.status) {            
  237.                         switch (error.response.status) {               
  238.                                 // 401: 未登录               
  239.                                 // 未登录则跳转登录页面,并携带当前页面的路径               
  240.                                 // 在登录成功后返回当前页面,这一步需要在登录页操作。               
  241.                                 case 401:                    
  242.                                         router.replace({                        
  243.                                                 path: '/login',                        
  244.                                                 query: { redirect: router.currentRoute.fullPath }
  245.                                         });
  246.                                         break;
  247.                                 // 403 token过期               
  248.                                 // 登录过期对用户进行提示               
  249.                                 // 清除本地token和清空vuex中token对象               
  250.                                 // 跳转登录页面               
  251.                                 case 403:                     
  252.                                         Toast({                        
  253.                                                 message: '登录过期,请重新登录',                        
  254.                                                 duration: 1000,                        
  255.                                                 forbidClick: true                    
  256.                                         });                    
  257.                                         // 清除token                    
  258.                                         localStorage.removeItem('token');                    
  259.                                         store.commit('loginSuccess', null);                    
  260.                                         // 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
  261.                                         setTimeout(() => {                        
  262.                                                 router.replace({                           
  263.                                                         path: '/login',                           
  264.                                                         query: {
  265.                                                                 redirect: router.currentRoute.fullPath
  266.                                                         }                        
  267.                                                 });                    
  268.                                         }, 1000);                    
  269.                                         break;
  270.                                 // 404请求不存在               
  271.                                 case 404:                    
  272.                                         Toast({                        
  273.                                                 message: '网络请求不存在',                        
  274.                                                 duration: 1500,                        
  275.                                                 forbidClick: true                    
  276.                                         });                    
  277.                                 break;               
  278.                                 // 其他错误,直接抛出错误提示               
  279.                                 default:                    
  280.                                         Toast({                        
  281.                                                 message: error.response.data.message,                        
  282.                                                 duration: 1500,                        
  283.                                                 forbidClick: true                    
  284.                                         });            
  285.                         }            
  286.                         return Promise.reject(error.response);        
  287.                 }      
  288.         }
  289. );
  290. /**
  291. * get方法,对应get请求
  292. * @param {String} url [请求的url地址]
  293. * @param {Object} params [请求时携带的参数]
  294. */
  295. export function get(url, params){   
  296.         return new Promise((resolve, reject) =>{        
  297.                 axios.get(url, {            
  298.                         params: params        
  299.                 })        
  300.                 .then(res => {            
  301.                         resolve(res.data);        
  302.                 })        
  303.                 .catch(err => {            
  304.                         reject(err.data)        
  305.                 })   
  306.         });
  307. }
  308. /**
  309. * post方法,对应post请求
  310. * @param {String} url [请求的url地址]
  311. * @param {Object} params [请求时携带的参数]
  312. */
  313. export function post(url, params) {   
  314.         return new Promise((resolve, reject) => {         
  315.                 axios.post(url, QS.stringify(params))        
  316.                 .then(res => {            
  317.                         resolve(res.data);        
  318.                 })        
  319.                 .catch(err => {            
  320.                         reject(err.data)        
  321.                 })   
  322.         });
  323. }
  324. axios的封装根据需求的不同而不同。
  325. 1.优化axios封装,去掉之前的get和post
  326. 2.断网情况处理
  327. 3.更加模块化的api管理
  328. 4.接口域名有多个的情况
  329. 5.api挂载到vue.prototype上省去引入的步骤
  330. http.js中axios封装的优化,先直接贴代码:
  331. /**
  332. * axios封装
  333. * 请求拦截、响应拦截、错误统一处理
  334. */
  335. import axios from 'axios';
  336. import router from '../router';
  337. import store from '../store/index';
  338. import { Toast } from 'vant';
  339. /**
  340. * 提示函数
  341. * 禁止点击蒙层、显示一秒后关闭
  342. */
  343. const tip = msg => {   
  344.         Toast({        
  345.                 message: msg,        
  346.                 duration: 1000,        
  347.                 forbidClick: true   
  348.         });
  349. }
  350. /**
  351. * 跳转登录页
  352. * 携带当前页面路由,以期在登录页面完成登录后返回当前页面
  353. */
  354. const toLogin = () => {
  355.         router.replace({
  356.                 path: '/login',        
  357.                 query: {
  358.                         redirect: router.currentRoute.fullPath
  359.                 }
  360.         });
  361. }
  362. /**
  363. * 请求失败后的错误统一处理
  364. * @param {Number} status 请求失败的状态码
  365. */
  366. const errorHandle = (status, other) => {
  367.         // 状态码判断
  368.         switch (status) {
  369.                 // 401: 未登录状态,跳转登录页
  370.                 case 401:
  371.                         toLogin();
  372.                         break;
  373.                 // 403 token过期
  374.                 // 清除token并跳转登录页
  375.                 case 403:
  376.                         tip('登录过期,请重新登录');
  377.                         localStorage.removeItem('token');
  378.                         store.commit('loginSuccess', null);
  379.                         setTimeout(() => {
  380.                                 toLogin();
  381.                         }, 1000);
  382.                         break;
  383.                 // 404请求不存在
  384.                 case 404:
  385.                         tip('请求的资源不存在');
  386.                         break;
  387.                 default:
  388.                         console.log(other);   
  389.                 }}
  390. // 创建axios实例
  391. var instance = axios.create({    timeout: 1000 * 12});
  392. // 设置post请求头
  393. instance.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
  394. /**
  395. * 请求拦截器
  396. * 每次请求前,如果存在token则在请求头中携带token
  397. */
  398. instance.interceptors.request.use(   
  399.         config => {        
  400.                 // 登录流程控制中,根据本地是否存在token判断用户的登录情况        
  401.                 // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token        
  402.                 // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码        
  403.                 // 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。        
  404.                 const token = store.state.token;        
  405.                 token && (config.headers.Authorization = token);        
  406.                 return config;   
  407.         },   
  408.         error => Promise.error(error))
  409. // 响应拦截器
  410. instance.interceptors.response.use(   
  411.         // 请求成功
  412.         res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),   
  413.         // 请求失败
  414.         error => {
  415.                 const { response } = error;
  416.                 if (response) {
  417.                         // 请求已发出,但是不在2xx的范围
  418.                         errorHandle(response.status, response.data.message);
  419.                         return Promise.reject(response);
  420.                 } else {
  421.                         // 处理断网的情况
  422.                         // eg:请求超时或断网时,更新state的network状态
  423.                         // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
  424.                         // 关于断网组件中的刷新重新获取数据,会在断网组件中说明
  425.                         if (!window.navigator.onLine) {
  426.                            store.commit('changeNetwork', false);
  427.                         } else {
  428.                                 return Promise.reject(error);
  429.                         }
  430.                 }
  431.         });
  432. export default instance;
  433. 这个axios和之前的大同小异,做了如下几点改变:
  434. 1.去掉了之前get和post方法的封装,通过创建一个axios实例然后export default方法导出,这样使用起来更灵活一些。
  435. 2.去掉了通过环境变量控制baseUrl的值。考虑到接口会有多个不同域名的情况,所以准备通过js变量来控制接口域名。这点具体在api里会介绍。
  436. 3.增加了请求超时,即断网状态的处理。说下思路,当断网时,通过更新vuex中network的状态来控制断网提示组件的显示隐藏。断网提示一般会有重新加载数据的操作,这步会在后面对应的地方介绍。
  437. 4.公用函数进行抽出,简化代码,尽量保证单一职责原则。
  438. 下面说下api这块,考虑到一下需求:
  439. 1.更加模块化
  440. 2.更方便多人开发,有效减少解决命名冲突
  441. 3.处理接口域名有多个情况
  442. 这里这里呢新建了一个api文件夹,里面有一个index.js和一个base.js,以及多个根据模块划分的接口js文件。index.js是一个api的出口,base.js管理接口域名,其他js则用来管理各个模块的接口。
  443. 先放index.js代码:
  444. /**
  445. * api接口的统一出口
  446. */
  447. // 文章模块接口
  448. import article from '@/api/article';
  449. // 其他模块的接口……
  450. // 导出接口
  451. export default {   
  452.         article,
  453.         // ……
  454. }
  455. index.js是一个api接口的出口,这样就可以把api接口根据功能划分为多个模块,利于多人协作开发,比如一个人只负责一个模块的开发等,还能方便每个模块中接口的命名哦。
  456. base.js:
  457. /**
  458. * 接口域名的管理
  459. */
  460. const base = {   
  461.         sq: 'https://xxxx111111.com/api/v1',   
  462.         bd: 'http://xxxxx22222.com/api'
  463. }
  464. export default base;
  465. 通过base.js来管理我们的接口域名,不管有多少个都可以通过这里进行接口的定义。即使修改起来,也是很方便的。
  466. 最后就是接口模块的说明,例如上面的article.js:
  467. /**
  468. * article模块接口列表
  469. */
  470. import base from './base'; // 导入接口域名列表
  471. import axios from '@/utils/http'; // 导入http中创建的axios实例
  472. import qs from 'qs'; // 根据需求是否导入qs模块
  473. const article = {   
  474.         // 新闻列表   
  475.         articleList () {        
  476.                 return axios.get(`${base.sq}/topics`);   
  477.         },   
  478.         // 新闻详情,演示   
  479.         articleDetail (id, params) {        
  480.                 return axios.get(`${base.sq}/topic/${id}`, {            
  481.                         params: params        
  482.                 });   
  483.         },
  484.         // post提交   
  485.         login (params) {        
  486.                 return axios.post(`${base.sq}/accesstoken`, qs.stringify(params));   
  487.         }
  488.         // 其他接口…………
  489. }
  490. export default article;
  491. 1.通过直接引入我们封装好的axios实例,然后定义接口、调用axios实例并返回,可以更灵活的使用axios,比如你可以对post请求时提交的数据进行一个qs序列化的处理等。
  492. 2.请求的配置更灵活,你可以针对某个需求进行一个不同的配置。关于配置的优先级,axios文档说的很清楚,这个顺序是:在 lib/defaults.js 找到的库的默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。后者将优先于前者。
  493. 3.restful风格的接口,也可以通过这种方式灵活的设置api接口地址。
  494. 最后,为了方便api的调用,我们需要将其挂载到vue的原型上。在main.js中:
  495. import Vue from 'vue'
  496. import App from './App'
  497. import router from './router' // 导入路由文件
  498. import store from './store' // 导入vuex文件
  499. import api from './api' // 导入api接口
  500. Vue.prototype.$api = api; // 将api挂载到vue的原型上
  501. 然后我们可以在页面中这样调用接口,eg:
  502. methods: {   
  503.         onLoad(id) {      
  504.                 this.$api.article.articleDetail(id, {        
  505.                         api: 123      
  506.                 }).then(res=> {
  507.                         // 执行某些操作      
  508.                 })   
  509.         }  
  510. }
  511. 再提一下断网的处理,这里只做一个简单的示例:
  512. <template>  
  513.             
  514.                      
  515.                         <h3>我没网了</h3>      
  516.                         刷新      
  517.                     
  518.                 <router-view/>      
  519.        
  520. </template>
  521. 这是app.vue,这里简单演示一下断网。在http.js中介绍了,我们会在断网的时候,来更新vue中network的状态,那么这里我们根据network的状态来判断是否需要加载这个断网组件。断网情况下,加载断网组件,不加载对应页面的组件。当点击刷新的时候,我们通过跳转refesh页面然后立即返回的方式来实现重新获取数据的操作。因此我们需要新建一个refresh.vue页面,并在其beforeRouteEnter钩子中再返回当前页面。
  522. // refresh.vue
  523. beforeRouteEnter (to, from, next) {
  524.         next(vm => {            
  525.                 vm.$router.replace(from.fullPath)        
  526.         })   
  527. }
复制代码
"""

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

北冰洋以北

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表