基于Vue对axios进行上层封装

基于Vue对axios进行上层封装

这的里的很多东西都是基于上一节 axios 讲解的,如果你不是对axios了解,请先看该文章

一、要真正拿axios进行实战之前要了解一些细节上的东西

(1) 先搞清楚请求发送的配置和拦截问题

  • axios的配置是可以合并的也有优先级,全局的axios.create创建的axios实例会自动吸取全局axios的配置

  • 修改任意axios实例的配置,都可以通过axios实例的defaults属性获取配置对象,再修改对应的配置项

  • axios的拦截器并不是可合并的,在每个单独的实例上的interceptor对应的过程(request,response)上调用use表示在单个实例添加对应拦截器;同样全局的axios.create创建的axios实例并不会吸收全局axios的拦截器

  • axios的拦截器的传入的拦截器其实内部和调用then方法一样,一个onFulfilled和onRejected函数,所以要注意函数返回的结果,或者是否需要抛出错误以改变调用顺序

(2) 令人头疼的Content-Type

axios默认是会自己对发送请求方法不同,和传入数据(请求体)的不同变更其 Content-Type,但是有的时候,这种默认的Content-Type并不是我们所期待的,于是需要手动配置。
先说一下不同方式axios的默认Content-Type

  • GET 没有,只有有请求体的请求方式才存在 Content-Type
  • POSTPUTPATCH application/x-www-form-urlencoded 如果传入的data请求体数据类型有文件或者FormData,那么自动转换为 application/form-data
  • DELETE text/plain 比较奇怪的是 Params 和Data都能传递参数

如果你希望使用Restful规范,delete只是text/plain 方式的肯定在后台接受参数会产生问题,这里两种解决反式

  • DELETE 的数据存放在 Params中,后端从URL获取,自然不会受到text/plain的影响
  • DELETE 的数据存放在 Data 中,Content-Type设置为application/x-www-form-urlencoded,后端可以正常的从请求体获取数据

其它的请求方法表现的都计较正常(正常情况下,如果你有特殊需求另外分析)

(3) 官网文档请求配置一些有点争议的地方

  • baseURL 说是识别相对地址,如果是相对路径就自动的加上,否则不加。实际测试中 baseURL 对 请求路径是http/https/域名开头的直接忽略,识别为绝对地址;对请求其它请求路径通过类似于path.join函数一样,于是相对路径带 “/” 还是不带可以随意

  • transformRequest 官方说只是最终影响POST、PUT、PATCH,但是实际测试有点奇怪:无论什么方法都会调用transformRequest的函数队列,但是只有拥有请求体的请求才会真正的处理数据并发出,得出结论:只要该请求允许携带请求体,transformRequest就有效

(4) 值得注意的几个细节

  • 请求配置中,responseType 默认是JSON,这也是为什么then返回的数据可以是对象

  • 请求配置中,paramsSerializer 负责序列化 Params的函数,它是有默认值,这也是为什么传入的Params对象最终以字符串拼接在URL上

  • withCredentials 这几乎是最重要的一点,很关键:withCredentials` 表示跨域请求时是否需要使用凭证,这个凭证就代表这Cookie和Token是否能被后端正确接受和写回给前端。默认的值为false表示跨域不携带。注意的是:只有在跨域情况下这个字段才有效果,非跨域下一定携带凭证 (如果有的话,并且不是手动去除)

Ajax跨域和Nginx反向代理 那篇有说明 withCredentials 具体怎么使用

二、开始上层封装

(1) 剥离拦截器

新建一个 axios-interceptors.js 文件
将你需要的拦截器写出这种形式

//你可能对option参数不太理解,等看完下一小段,你就能明白
const reqCommon = {
    /**
     * 发送请求之前做些什么
     * @param config axios config
     * @returns {*}
     */
    onFulfilled(config, option) {
        return config
    },
    /**
     * 请求出错时做点什么
     * @param error 错误对象
     * @returns {Promise<never>}
     */
    onRejected(error, option) {
        return Promise.reject(error)
    }
}

//响应拦截
const resCommon  ={
    onFulfilled(response, option) {
        return response
    },
    onRejected(error, option) {
        const { response, request } = error
        if (response) { //响应是否存在
            switch (response.status) {
                case 400:
                    error.message = '错误请求'
                    break
                case 401:
                    error.message = '未授权,请重新登录'
                    break
                case 403:
                    error.message = '拒绝访问'
                    break
                case 404:
                    error.message = '请求错误,未找到该资源'
                    break
                case 405:
                    error.message = '请求方法未允许'
                    break
                case 408:
                    error.message = '请求超时'
                    break
                case 500:
                    error.message = '服务器端出错'
                    break
                case 501:
                    error.message = '网络未实现'
                    break
                case 502:
                    error.message = '网络错误'
                    break
                case 503:
                    error.message = '服务不可用'
                    break
                case 504:
                    error.message = '网络超时'
                    break
                case 505:
                    error.message = 'http版本不支持该请求'
                    break
                default:
                    error.message = `未知错误${error.response.status}`
            }
        }else if(request){// 响应不存在,很可能是请求都未发出
            if(!window.navigator.onLine) { //发现是断网了
                error.message = "网络未连接"
            }
        }else {
            error.message = "配置请求发生了错误"
        }
        return Promise.reject(error)
    }
}

export default {
    request: [reqCommon], // 请求拦截
    response: [resCommon] // 响应拦截
}

可以看到request队列中的元素是一个对象,对象有两个方法,都将在被axios实例拦截时调用use方法传入的两个参数,表示成功和失败;response队列也是如此

(2) 生成 loadInterceptors => 加载拦截器函数

//httpRequest => axios实例;interceptors => 分离出来的拦截器;option => 闭包参数
export function loadInterceptors(httpRequest, interceptors, option) {
    const {request, response} = interceptors
    // 加载请求拦截器
    request.forEach(item => {
        let {onFulfilled, onRejected} = item
        if (!onFulfilled || typeof onFulfilled !== 'function') {
            onFulfilled = config => config
        }
        if (!onRejected || typeof onRejected !== 'function') {
            onRejected = error => Promise.reject(error)
        }
        httpRequest.interceptors.request.use(
            //生成闭包!!!这样在拦截器调用的时候能够有option参数
            config => onFulfilled(config, option),
            config => onRejected(config, option)
        )
    })
    // 加载响应拦截器
    response.forEach(item => {
        let {onFulfilled, onRejected} = item
        if (!onFulfilled || typeof onFulfilled !== 'function') {
            onFulfilled = response => response
        }
        if (!onRejected || typeof onRejected !== 'function') {
            onRejected = error => Promise.reject(error)
        }
        httpRequest.interceptors.request.use(
            config => onFulfilled(config, option),
            config => onRejected(config, option)
        )
    })
}

(2) 生成axios实例,并配置对应的拦截器

//config生成新的axios实例的配置;interceptors要绑定的拦截器;
//requestMethods在实例上绑定的自定义方法数组
function createAxios({
    config, interceptors, requestMethods
}) {
    //创建axios实例
    const httpRequest = axios.create(config)
    //加载拦截器,并绑定到对于的axios实例
    loadInterceptors(httpRequest, interceptors) 
     //遍历方法数组
    for(let method of requestMethods) {
        if(method === 'upload') {
            httpRequest['$Upload'] = requestMethodCreator('post')
            continue
        }
        if(method === 'delete') {
            httpRequest['$' + method.toUpperCase()] = requestMethodCreator(method, {
                headers: {'Content-Type': 'application/x-www-form-urlencoded'}
            })
            continue
        }
        httpRequest['$' + method.toUpperCase()] = requestMethodCreator(method)
    }
    return httpRequest
}

(3) 一次性的自定义拦截器 => beforeRequest、onRequest、atferRequest

//导出请求方法创建器
function requestMethodCreator(method, outConfig) {
    return function baseRequestMethod(API, body, option = {}) {
        let { beforeRequest, onRequest, atferRequest, ...innerConfig } = option
        //写一个自己的拦截器
        const defaultConfig = {
            method,
            url: API,
        }
        //选择请求体
        defaultConfig[method === 'get'? 'params' : 'data'] = body
        //合并配置
        Object.assign(defaultConfig, innerConfig, outConfig)
        //执行自定义拦截
        beforeRequest && beforeRequest()
        let result = this(defaultConfig)
        //真正发送请求
        if(onRequest) {
            result = result.then(onRequest, errorReason => Promise.reject(errorReason))
            if(atferRequest) {
                result = result.finally(atferRequest)
            }
        }
        return result
    }
}

(4) 使用导出utils的http-request.js文件

import axios from "axios";
import qs from "qs";
import {createAxios} from './create-axios'
import interceptors from './axios-interceptors'

axios.defaults.timeout = 3000
axios.defaults.responseType = 'json'
axios.defaults.transformRequest = [function (data){
    console.log(data)
    data = {message: '被修改了'}
    if(data instanceof FormData) return data
    data = qs.stringify(data)
    return data;
}]

//服务请求路径前缀
const {
    VUE_APP_SERVICE_PROXY_URL: SERVICE_PROXY_URL,
    VUE_APP_SERVICE_API_PREFIX: SERVICE_API_PREFIX,
    VUE_APP_UPLOAD_PROXY_URL: UPLOAD_PROXY_URL,
    VUE_APP_UPLOAD_API_PREFIX: UPLOAD_API_PREFIX,
    VUE_APP_VERTIFY_PROXY_URL: VERTIFY_PROXY_URL,
    VUE_APP_VERTIFY_API_PREFIX: VERTIFY_API_PREFIX
} = process.env

// SERVICE_PROXY_URL 请求路径
export const $BASE_URL = '/' + SERVICE_PROXY_URL + '/' + SERVICE_API_PREFIX

export const $VERTIFY_URL = '/' + VERTIFY_PROXY_URL + '/' + VERTIFY_API_PREFIX

export const $UPLOAD_URL = '/' + UPLOAD_PROXY_URL + '/'

export const httpRequest = createAxios({
    config: {baseURL: $BASE_URL},
    interceptors,
    requestMethods: ['get', 'post', 'put', 'delete', 'upload']
})

export const httpVertify = createAxios({
    config: {baseURL: $VERTIFY_URL},
    interceptors,
    requestMethods: ['get']
})

export const httpUpload = createAxios({
    config: {baseURL: $UPLOAD_URL},
    interceptors,
    requestMethods: ['upload']
})

export default {
    httpRequest, httpVertify, httpUpload
}

(5) vue.config.js和.env文件配置

.env

## 这里是配置环境变量,因为可能有多个代理,所以进行约束 =>  括号内表示你的代理地址 名称
## VUE_APP_[]_URL 代理目标地址
## VUE_APP_[]_PROXY_URL  代理使用 标识符
## VUE_APP_[]__API_PREFIX  代理真实API接口的前缀 标识符,默认为空

## 举例
VUE_APP_SERVICE_URL=http://localhost:3000
## 代理服务器地址路径
VUE_APP_SERVICE_PROXY_URL=BLOG_API
## 请求前缀
VUE_APP_SERVICE_API_PREFIX=api/v2.0/

## 简书身份认证
## 真实地址
VUE_APP_VERTIFY_URL=https://www.jianshu.com
## 代理服务器地址值路径
VUE_APP_VERTIFY_PROXY_URL=VERTIFY
VUE_APP_VERTIFY_API_PREFIX=

## 上传文件到简书的地址
## 真实地址
VUE_APP_UPLOAD_URL=https://upload.qiniup.com
## 代理服务器地址值路径
VUE_APP_UPLOAD_PROXY_URL=UPLOAD
VUE_APP_UPLOAD_API_PREFIX=

## 转发请求,安全获取简书的图片
## 真实路径
VUE_APP_JIANSHU_IMAGE_URL=https://upload-images.jianshu.io
## 代理字段
VUE_APP_JIANSHU_IMAGE_PROXY_URL=GET_JIAN_SHU_IMAGE
VUE_APP_JIANSHU_IMAGE_API_PREFIX=

vue.config.js

const path = require('path')

function resolve(dir) {
  return path.join(__dirname, dir)
}

//自动处理多个代理路径
const proxyArr = ['SERVICE','UPLOAD','JIANSHU_IMAGE', 'VERTIFY']
const proxy = {} //代理对象
proxyArr.forEach((proxyname)=>{
  const targetUrl= process.env['VUE_APP_' + proxyname + '_URL']
  const targetProxyUrl= process.env['VUE_APP_' + proxyname + '_PROXY_URL']
  proxy['/' + targetProxyUrl] = {
    target: targetUrl,
    secure: false,
    changeOrigin: true,
    pathRewrite: {
      ['^/'+targetProxyUrl]: ''
    }
  }
})

module.exports = {
  ...config,
  devServer: {
    port: '3000',
    proxy
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值