Skip to content

about

https://github.com/axios/axios

安装

yarn add axios@next

npm install axios --save

快速上手

来看下基本的使用。

1. 创建一个axios.js文件,我通常把它创建在src/utils/axios.js

javascript
import axios from 'axios'

// $开头以示区分,你随便命名都行,
const $axios = axios.create({
    baseURL: "http://httpbin.org",
    timeout: 2000
})

export default $axios

2. 在mian.js中挂在到vue对象中,可以在全局通过this来调用,非常方便

javascript
import { createApp } from 'vue'
import App from './App.vue'
// 就这一行
import $axios from "./utils/axios";

import router from "./router";

const app = createApp(App)

app.use(router)
// 将axios对象绑定到vue的app对象上,后面方便调用
app.config.globalProperties.$axios = $axios;
app.mount('#app')  // 挂在放最后

3. 使用,有两种方式

html
<template>
    <div>
        <button @click="btn1">点我发送请求</button>
        <button @click="btn2">点我发送请求</button>
    </div>
</template>
<script setup>

// 方式1,直接导入axios对象,然后直接使用就可以了
import $axios from "./plugins/axios";

function btn1() {
    // 拼接后的完整的url: http://httpbin.org/get
  $axios.get('get').then((res)=>{
    console.log(res)
  })
}
// 方式2,因为在main.js中绑定到了app对象上,这里就可以通过这个app来调用
import {getCurrentInstance} from "vue";
// 这个proxy就相当于vue2中的this
// 如果你在页面中到处都会用到这个proxy,那么用到axios就用proxy调用就行,不用在单独如方式1中,还要单独导入axios了
const {proxy} = getCurrentInstance()
function btn2() {
    // 拼接后的完整的url: http://httpbin.org/get
    proxy.$axios.post('post', {name:"zhangkai"}).then((res)=>{
        console.log(res)
        // 这里也可以拿到自定义的配置,比如我们最开始配置的base_url
        console.log(proxy.$axios.defaults.baseURL)
    })
}
</script>
<style scoped>
</style>

请求拦截器

javascript
import axios from 'axios'
import router from '../router/index'
import store from '../store/index'
import {ElMessage} from 'element-plus'

// $开头以示区分,你随便命名都行

let config = {
    baseURL: "http://127.0.0.1:8200/",
    // timeout: 3000
}
const $axios = axios.create(config)

$axios.interceptors.request.use(
    function (config) {
        // 结合jwt token,如果有token就在请求头中携带上token
        const token = localStorage.getItem('token');
        if (token){
            // 如果前端报错下面两个错误(我目前发现的):
            // 第1个: TypeError: Cannot set properties of undefined (setting 'Authorization')
            // 第2个: Uncaught (in promise) TypeError: error.response is undefined
            // config.headers.common['Authorization'] = token;
            // 就换成下面的写法
            config.headers.Authorization = token;
        }
        // 在axios请求拦截器中,为每一个请求,添加请求头,注意,如果后端是Django的话,请求头中的key,都是首字母大写的,比如下面的k1,后端会变成K1,但值不变
        // 下面两种方式都可以
        config.headers["k1"] = 'v1';
        config.headers.k2 = 'v2';
        // 在axios请求拦截器中,为每一个请求,添加请求参数,也就是放到url上当做请求参数了
        // /api/login/?k3=v3&k4=v4
        config.params = {
            k3: 'v3',
            k4: 'v4'
        }
        // 必须返回config
        return config
    }
)

拦截器

html
"use strict";

import axios from "axios";
import router from "@/router";
import store from "@/store";
import {ElMessage} from 'element-plus'

// Full config:  https://github.com/axios/axios#request-config
axios.defaults.baseURL = "http://127.0.0.1:8000/";
// axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

let config = {
    // baseURL: process.env.baseURL || process.env.apiUrl || ""
    // timeout: 60 * 1000, // Timeout
    // withCredentials: true, // Check cross-site Access-Control
};

const _axios = axios.create(config);

_axios.interceptors.request.use(
    function (config) {
        // Do something before request is sent
        const token = localStorage.getItem("token");
        if (token) {
            // 如果前端报错下面两个错误(我目前发现的):
            // 第1个: TypeError: Cannot set properties of undefined (setting 'Authorization')
            // 第2个: Uncaught (in promise) TypeError: error.response is undefined
            config.headers.common['Authorization'] = token;
            // 就换成下面的写法
            // config.headers.Authorization = token;
        }
        return config;
    }
);

// Add a response interceptor
_axios.interceptors.response.use(
    function (response) {
        // Do something with response data
        return response;
    },
    function (error) {
        // Do something with response error
        console.log(error.response.status);
        if (error.response.status === 401) {
            store.commit("logout");
            router.replace({name: "Login"});
            ElMessage.error("认证失败,请重新登录");
        }
        return Promise.reject(error);
    }
);

// export function installAxios(Vue) {
//     Vue.config.globalProperties.$axios = _axios;
// }

export default _axios;

请求携带cookie

我们来做个真实的演示。

基于Django3.2+restframework+vue3+axios+vue3-cookies来完成的。

前端

App.vue

html
<script setup>
    import {getCurrentInstance} from 'vue'
    const {proxy} = getCurrentInstance()
    proxy.$axios.get('http://127.0.0.1:8200/api/test/').then((res)=>{
        console.log(res.data.code)
        console.log(proxy.$cookies.keys());
        // 指定httponly=true的cookie获取不到
        console.log(proxy.$cookies.get('user'));  // null
        // 没有指定httponly=true的cookie是能获取到的
        console.log(777, proxy.$cookies.get('name')); // likai
        // 很明显,document.cookie中同样获取不到指定了httponly=true的cookie
        console.log(document.cookie)  // _ga=GA1.1.181234010.1667112062; csrftoken=FVsQQd0d23YKuk7Zk0x4FxNI8MBZIALshISquhqyVIPjnW5st9rQXrpShjcnrgQ8; k5=v5; name=likai
    })
</script>
<template>
    <div></div>
</template>
<style scoped>
</style>

main.js

javascript
import { createApp } from 'vue'
// import './style.css'
import App from './App.vue'

import $axios from "./utils/axios";

// 引入
import VueCookies from 'vue3-cookies'

const app = createApp(App)

// use一下VueCookies
app.use(VueCookies)

// 添加到vue对象上,可以在全局通过 this.$axios来调用
app.config.globalProperties.$axios = $axios;
app.mount('#app')  // 挂在放最后

axios.js

javascript
import axios from 'axios'

const $axios = axios.create({
    baseURL: "http://httpbin.org",
    timeout: 2000,
    withCredentials: true   // 必须指定这个参数,否则axios才能携带参数
})

export default $axios

后端

首先用中间件解决跨域问题,我这里用的自定义的中间件设置响应头处理的。

middlewares.py,注意这个中间件主要就是为了演示axios携带cookie特别搭配的,如果普通的axios请求,你就要把Access-Control-Allow-Origin的值换成*,还有去掉Access-Control-Allow-Credentials

python
from django.utils.deprecation import MiddlewareMixin
from django.http import HttpResponse

class AuthMiddleware(MiddlewareMixin):
	""" 在这里处理前后端分离的跨域问题,这样前端不用任何处理 """
	def process_request(self, request):
		""" 在这里处理预检请求 """
		# print(111, request.headers)
		if request.method == "OPTIONS":
			response = HttpResponse("")
			response["Access-Control-Allow-Origin"] = "http://127.0.0.1:5173"
			# response["Access-Control-Allow-Origin"] = "*"
			response["Access-Control-Allow-Headers"] = "*"
			response["Access-Control-Allow-Methods"] = "*"
			response["Access-Control-Allow-Credentials"] = "true"
			return response  # 当发现请求是预检的options请求时,直接返回不做拦截
	
	def process_response(self, request, response):
		""" 其它复杂请求,都添加上各种响应头给客户端返回 """
		# response["Access-Control-Allow-Origin"] = "*"  # 如果axios请求不需要带cookie,用这个 "*" 就好了
        # 下面一行是为了允许axios携带cookie,必须指定前端所在的域名
		response["Access-Control-Allow-Origin"] = "http://127.0.0.1:5173"
		# response["Access-Control-Allow-Origin"] = "*"
		response["Access-Control-Allow-Headers"] = "*"
		response["Access-Control-Allow-Methods"] = "*"
		response["Access-Control-Allow-Credentials"] = "true"  # 这个必须是"true",否则人家不认
		return response

settings.py

python
MIDDLEWARE = [
	'utils.middlewares.AuthMiddleware',  # 跨域的中间件放到最上面
	'django.middleware.security.SecurityMiddleware',
	# 'django.contrib.sessions.middleware.SessionMiddleware',
	'django.middleware.common.CommonMiddleware',
	# 'django.middleware.csrf.CsrfViewMiddleware',
	# 'django.contrib.authentications.middleware.AuthenticationMiddleware',
	# 'django.contrib.messages.middleware.MessageMiddleware',
	'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

views.py

python
from rest_framework.views import APIView
from rest_framework.response import Response

class TestView(APIView):
	def get(self, request):
		# 因为前端设置了axios允许携带cookie,我们从后端传过去的cookie,后端也都能拿到
		print(request.COOKIES.get("user"))  # zhangkai
		print(request.COOKIES.get("name"))  # likai
		response = Response({'code': 0})
		response.set_cookie('user', "zhangkai", expires=60, httponly=True)
		response.set_cookie('name', "likai", expires=60, httponly=False)
		return response

urls.py

python
from django.urls import path, re_path
from api.views import account, basic, wallet

urlpatterns = [
    path('api/test/', account.TestView.as_view())
]

get请求中携带array和object类型的数据

通常axios的get请求,如果携带参数,也是这么携带的:

javascript
function xx() {
    let data = {k1: 'v1', k2: 'v2'}
    axios.get("http://127.0.0.1:8200/api/test/", {
        params: data
    }).then(res => {
        console.log(111, res)
    })
}

后端接受也非常方便:

python
from rest_framework.views import APIView

class TestView(APIView):
    def get(self, request):
        # 原始的QueryDict
        print(request.query_params)
        print(request.query_params['k1'], request.query_params['k2'])
        """
        # 由打印日志,可以看到,参数都被axios自动拼接到了URL后面了
        [18/Sep/2023 17:40:10] "GET /api/test/?k1=v1&k2=v2 HTTP/1.1" 200 4
        <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
        v1 v2
        """
        return Response("OK")

但是,有一次,要对接一个老的项目,请求方式是get,但是请求中,除了普通的参数还要携带数组和对象,而且后端还希望能比较方便处理数组和对象,为啥这么说呢?因为axios默认发送get请求,携带array和object类型的数据的话。

来看示例,前端还是正常发送的话:

javascript
function xx() {
    let data = {
        k1: 'v1', k2: "v2",
        filter: {
            k3: 'v3', k4: 'v4', k5: 'v5',
            k6: {
                k7: "v7", k8: "v8"
            }
        },
        arr: [1, 2, 3]
    }
    proxy.$axios.get("http://127.0.0.1:8200/api/test/", {
        params: data
    }).then(res => {
        console.log(111, res)
    })
}
xx()

后端接收:

python
from rest_framework.views import APIView

class TestView(APIView):
    def get(self, request):
        # 原始的QueryDict
        print(request.query_params)
        # # 获取普通参数
        print(request.query_params['k1'], request.query_params['k2'])
        """
        # 由打印日志,可以看到,普通参数还是原来的套路,但是数组和对象发到后端,可就难办了....
        <QueryDict: {'k1': ['v1'], 'k2': ['v2'], 'filter[k3]': ['v3'], 'filter[k4]': ['v4'], 'filter[k5]': ['v5'], 'filter[k6][k7]': ['v7'], 'filter[k6][k8]': ['v8'], 'arr[]': ['1', '2', '3']}>
        v1 v2
        """
        return Response("OK")

没法处理了。

对于这种情况,我通常是建议要携带的参数相对复杂的话,就将get请求改为post请求,请求体携带这些都比较方便了。

但是谁让项目是个老项目,人家开发又比较硬呢......

只能咬牙改造了,这里用到了URL.searchParams的相关知识,参考:https://developer.mozilla.org/zh-CN/docs/Web/API/URL/searchParams

我这里直接就上代码了,前端代码:

javascript
function getSearchParams(oldUrl, objData) {
    // 将传来的get请求的URL和参数处理一下
    // 因为get请求,默认是不能传递数组和object的,所以要经过这个函数的特殊处理
    // 经过循环处理,拼接到原有的URL后面,最终返回处理的好的URL
    let urlObj = new URL(oldUrl)
    for (const i in objData){
        urlObj.searchParams.set(i, JSON.stringify(objData[i]))
    }
    return urlObj.href
}
function xx() {
    let data = {
        k1: 'v1', k2: "v2",
        filter: {
            k3: 'v3', k4: 'v4', k5: 'v5',
            k6: {
                k7: "v7", k8: "v8"
            }
        },
        arr: [1, 2, 3]
    }
    let newUrl = getSearchParams("http://127.0.0.1:8200/api/test/", data)
    // 注意,经过getSearchParams函数处理好的URL和参数,你下面在发送的时候,直接往新的URL发送请求就行了,就不要再重复携带params了
    axios.get(newUrl, {}).then(res => {
        console.log(111, res)
    })
}
xx()

后端接收:

python
import json
from rest_framework.views import APIView

class TestView(APIView):
    def get(self, request):
        # 原始的QueryDict
        print(request.query_params)
        # # 获取普通参数
        print(request.query_params['k1'], request.query_params['k2'])
        # 将JavaScript的数组类型转为列表
        print(request.query_params['arr'], json.loads(request.query_params['arr']))
        # 将JavaScript的object转为字典
        print(request.query_params['filter'], json.loads(request.query_params['filter']))
        """
        <QueryDict: {'k1': ['"v1"'], 'k2': ['"v2"'], 'filter': ['{"k3":"v3","k4":"v4","k5":"v5","k6":{"k7":"v7","k8":"v8"}}'], 'arr': ['[1,2,3]']}>
        "v1" "v2"
        [1,2,3] [1, 2, 3]
        {"k3":"v3","k4":"v4","k5":"v5","k6":{"k7":"v7","k8":"v8"}} {'k3': 'v3', 'k4': 'v4', 'k5': 'v5', 'k6': {'k7': 'v7', 'k8': 'v8'}}
        """
        return Response("OK")

这次看着就正常多了,后端也方便处理了,如果后端再不满意的话,能动手的就不要再吵吵了。

常见报错

TypeError: Cannot set properties of undefined (setting 'Authorization')

"vue": "^3.2.41" + "axios": "^1.2.0"

报错现象: 1832671020780093440.png 报错代码行: 1832671022390706176.png 修改: 1832671024693379072.png

javascript
_axios.interceptors.request.use(
    function(config) {
        // Do something before request is sent
        const token = localStorage.getItem("token");
        console.log(111, token)
        if (token) {
            // config.headers.common['Authorization'] = token;
            // 上面那行报错,TypeError: Cannot set properties of undefined (setting 'Authorization'),就改成下面的就行
            config.headers.Authorization = token;
        }
        return config;
    }
);

其实,config.headers.common['Authorization'] = token;这么写平常是没问题的,但有些情况习下,就不行,只好改为config.headers.Authorization = token;。 参考:https://blog.csdn.net/taotu_tao/article/details/126930035

Uncaught (in promise) TypeError: error.response is undefined

"vue": "^3.2.41" + "axios": "^1.2.0"

如果你在运行项目,访问某个页面,控制台报错error.response is undefined,如下图: 1832671029151924224.png 那么很有可能是axios的请求拦截器部分代码写的有问题,导致响应拦截器出了问题,然后引起的报错,那么你可以尝试如下方式修改axios的请求拦截器代码:

javascript
"use strict";
import axios from "axios";
import router from "@/router";
import store from "@/store";
import {ElMessage} from 'element-plus'

axios.defaults.baseURL = "http://127.0.0.1:8000/";
let config = {
    // baseURL: process.env.baseURL || process.env.apiUrl || ""
    // timeout: 60 * 1000, // Timeout
    // withCredentials: true, // Check cross-site Access-Control
};

const _axios = axios.create(config);

_axios.interceptors.request.use(
    function (config) {
        // Do something before request is sent
        const token = localStorage.getItem("token");
        if (token) {
            // 如果前端报错下面两个错误(我目前发现的):
            // 第1个: TypeError: Cannot set properties of undefined (setting 'Authorization')
            // 第2个: Uncaught (in promise) TypeError: error.response is undefined
            config.headers.common['Authorization'] = token;
            // 就换成下面的写法
            // config.headers.Authorization = token;
        }
        return config;
    }
);

// Add a response interceptor
_axios.interceptors.response.use(
    function (response) {
        // Do something with response data
        return response;
    },
    function (error) {
        // Do something with response error
        console.log(error.response.status);
        if (error.response.status === 401) {
            store.commit("logout");
            router.replace({name: "Login"});
            ElMessage.error("认证失败,请重新登录");
        }
        return Promise.reject(error);
    }
);
export default _axios;