about
安装
yarn add axios@next
npm install axios --save
快速上手
来看下基本的使用。
1. 创建一个axios.js文件,我通常把它创建在src/utils/axios.js
import axios from 'axios'
// $开头以示区分,你随便命名都行,
const $axios = axios.create({
baseURL: "http://httpbin.org",
timeout: 2000
})
export default $axios
2. 在mian.js中挂在到vue对象中,可以在全局通过this来调用,非常方便
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. 使用,有两种方式
<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>
请求拦截器
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
}
)
拦截器
"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
:
<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
:
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
:
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
。
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
:
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
:
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
:
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请求,如果携带参数,也是这么携带的:
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)
})
}
后端接受也非常方便:
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类型的数据的话。
来看示例,前端还是正常发送的话:
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()
后端接收:
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
我这里直接就上代码了,前端代码:
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()
后端接收:
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"
报错现象: 报错代码行: 修改:
_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
,如下图: 那么很有可能是axios的请求拦截器部分代码写的有问题,导致响应拦截器出了问题,然后引起的报错,那么你可以尝试如下方式修改axios的请求拦截器代码:
"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;