Axios指北

如题,结合官方文档与网络帖子,利用AI整理

Axios 使用指南

Date: October 16, 2025
Code: https://github.com/axios/axios


目录

  1. 引言

  2. Axios 基础介绍

  3. 安装与环境配置

  4. 基础使用方法

  5. 一般场景应用

  6. Vue 项目中的使用

  7. Axios 封装最佳实践

  8. 网络知识科普

  9. 安全性考虑

  10. 性能优化

  11. 常见问题与解决方案

  12. 总结


引言

在现代前端开发中,与后端服务器进行数据交互是不可或缺的环节。Axios 作为一款基于 Promise 的 HTTP 客户端,已经成为众多开发者的首选工具。本指南将从基础概念到高级应用,全面介绍 Axios 的使用方法,帮助开发者构建更加健壮和高效的网络请求系统。


Axios 基础介绍

什么是 Axios

Axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和 Node.js 中。它本质上是对原生 XMLHttpRequest 的封装,但提供了更简洁的 API 和更强大的功能。

核心特性

  • 支持 Promise API:使用现代化的异步编程模式

  • 拦截请求和响应:统一处理请求前和响应后的逻辑

  • 自动转换 JSON 数据:无需手动处理数据格式转换

  • 取消请求:支持中止长时间未响应的请求

  • 客户端防御 XSRF:提供安全防护机制

  • 跨平台支持:同时支持浏览器和 Node.js 环境

为什么选择 Axios

相比原生 XMLHttpRequest 和 fetch API,Axios 具有以下优势:

  1. 更简洁的 API:链式调用,代码更易读

  2. 更好的错误处理:统一的错误处理机制

  3. 拦截器功能:全局请求/响应处理

  4. 取消请求:避免内存泄漏和不必要的网络请求

  5. 浏览器兼容性:支持更多浏览器版本


安装与环境配置

使用 npm 安装

1
2
3
4
5
npm install axios
# 或者
yarn add axios
# 或者
pnpm add axios

使用 CDN

1
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

环境配置

在项目中创建基础配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/utils/axiosConfig.js
import axios from 'axios';

// 创建 axios 实例
const axiosInstance = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:3000/api',
timeout: 10000, // 超时时间
headers: {
'Content-Type': 'application/json'
}
});

export default axiosInstance;

基础使用方法

发起 GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import axios from 'axios';

// 基础 GET 请求
axios.get('/users')
.then(response => {
console.log(response.data);
})
.catch(error => {
console.error('Error:', error);
});

// 带参数的 GET 请求
axios.get('/users', {
params: {
id: 123,
name: 'John'
}
})
.then(response => {
console.log(response.data);
});

// 使用 async/await
async function getUser() {
try {
const response = await axios.get('/users/123');
console.log(response.data);
} catch (error) {
console.error('Error:', error);
}
}

发起 POST 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 基础 POST 请求
axios.post('/users', {
username: 'john_doe',
email: 'john@example.com',
password: 'password123'
})
.then(response => {
console.log('User created:', response.data);
})
.catch(error => {
console.error('Error:', error);
});

// 带请求头的 POST 请求
axios.post('/login',
{ username: 'admin', password: 'admin123' },
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}
)
.then(response => {
console.log('Login successful:', response.data);
});

其他请求方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// PUT 请求 - 更新整个资源
axios.put('/users/123', {
name: 'John Updated',
email: 'john.updated@example.com'
});

// PATCH 请求 - 部分更新
axios.patch('/users/123', {
email: 'new.email@example.com'
});

// DELETE 请求
axios.delete('/users/123');

// HEAD 请求 - 获取响应头
axios.head('/users/123');

// OPTIONS 请求 - 获取资源支持的方法
axios.options('/users');

一般场景应用

处理并发请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 同时发起多个请求
function getUserAccount() {
return axios.get('/user/123');
}

function getUserPermissions() {
return axios.get('/user/123/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread((account, permissions) => {
console.log('Account:', account.data);
console.log('Permissions:', permissions.data);
}));

// 使用 Promise.all
Promise.all([
axios.get('/users'),
axios.get('/posts'),
axios.get('/comments')
])
.then(([usersRes, postsRes, commentsRes]) => {
console.log('Users:', usersRes.data);
console.log('Posts:', postsRes.data);
console.log('Comments:', commentsRes.data);
});

请求配置详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
const config = {
url: '/users',
method: 'get', // default
baseURL: 'https://api.example.com',
transformRequest: [function (data, headers) {
// 对请求数据进行转换处理
return data;
}],
transformResponse: [function (data) {
// 对响应数据进行转换处理
return data;
}],
headers: {'X-Requested-With': 'XMLHttpRequest'},
params: {
ID: 12345
},
paramsSerializer: function(params) {
return Qs.stringify(params, {arrayFormat: 'brackets'})
},
data: {
firstName: 'Fred'
},
timeout: 1000,
withCredentials: false, // default
adapter: function (config) {
/* ... */
},
auth: {
username: 'janedoe',
password: 's00pers3cret'
},
responseType: 'json', // default
responseEncoding: 'utf8', // default
xsrfCookieName: 'XSRF-TOKEN', // default
xsrfHeaderName: 'X-XSRF-TOKEN', // default
onUploadProgress: function (progressEvent) {
// 处理上传进度
},
onDownloadProgress: function (progressEvent) {
// 处理下载进度
},
maxContentLength: 2000,
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
maxRedirects: 5, // default
socketPath: null, // default
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
proxy: {
host: '127.0.0.1',
port: 9000,
auth: {
username: 'mikeymike',
password: 'rapunz3l'
}
},
cancelToken: new axios.CancelToken(function (cancel) {
})
};

axios(config)
.then(response => {
console.log(response.data);
});

错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
axios.get('/users')
.then(response => {
console.log(response.data);
})
.catch(error => {
if (error.response) {
// 请求已发出,服务器返回状态码不在 2xx 范围内
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求已发出,但没有收到响应
console.log(error.request);
} else {
// 发生了一些设置请求时的错误
console.log('Error:', error.message);
}
console.log(error.config);
});

Vue 项目中的使用

Vue 3 中的基础使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import axios from 'axios';

const app = createApp(App);

// 全局配置 axios
axios.defaults.baseURL = process.env.VUE_APP_API_URL;
axios.defaults.timeout = 10000;

// 添加到全局属性
app.config.globalProperties.$axios = axios;

app.mount('#app');

在组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div class="user-profile">
<div v-if="loading" class="loading">Loading...</div>
<div v-else-if="error" class="error">{{ error }}</div>
<div v-else class="profile">
<h2>{{ user.name }}</h2>
<p>{{ user.email }}</p>
</div>
</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';

const user = ref(null);
const loading = ref(false);
const error = ref(null);

onMounted(async () => {
loading.value = true;
try {
const response = await axios.get('/users/profile');
user.value = response.data;
} catch (err) {
error.value = 'Failed to load user data';
console.error(err);
} finally {
loading.value = false;
}
});
</script>

<style scoped>
.loading { color: #666; }
.error { color: #dc3545; }
.profile { padding: 20px; }
</style>

结合 Pinia 进行状态管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// src/stores/userStore.js
import { defineStore } from 'pinia';
import axios from '@/utils/axiosConfig';

export const useUserStore = defineStore('user', {
state: () => ({
user: null,
loading: false,
error: null
}),

actions: {
async fetchUserProfile() {
this.loading = true;
this.error = null;

try {
const response = await axios.get('/users/profile');
this.user = response.data;
return response.data;
} catch (err) {
this.error = err.message || 'Failed to fetch user profile';
throw err;
} finally {
this.loading = false;
}
},

async updateUserProfile(data) {
this.loading = true;
this.error = null;

try {
const response = await axios.put('/users/profile', data);
this.user = response.data;
return response.data;
} catch (err) {
this.error = err.message || 'Failed to update profile';
throw err;
} finally {
this.loading = false;
}
}
}
});

在 Vue Router 中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import axios from '@/utils/axiosConfig';

const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue'),
beforeEnter: async (to, from, next) => {
try {
// 检查用户是否已登录
const response = await axios.get('/auth/me');
if (response.data.isAuthenticated) {
next();
} else {
next('/login');
}
} catch (error) {
next('/login');
}
}
}
];

const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
});

export default router;

Axios 封装最佳实践

创建基础请求类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// src/utils/request.js
import axios from 'axios';
import { ElMessage, ElLoading } from 'element-plus';

class Request {
constructor() {
this.instance = axios.create({
baseURL: process.env.VUE_APP_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
});

this.setupInterceptors();
this.loadingInstance = null;
this.requestCount = 0;
}

// 设置拦截器
setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config) => {
this.showLoading(config);

// 添加认证 token
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}

return config;
},
(error) => {
this.hideLoading();
return Promise.reject(error);
}
);

// 响应拦截器
this.instance.interceptors.response.use(
(response) => {
this.hideLoading();
return this.handleResponse(response);
},
(error) => {
this.hideLoading();
return this.handleError(error);
}
);
}

// 显示加载状态
showLoading(config) {
if (config.showLoading !== false) {
this.requestCount++;
if (this.requestCount === 1) {
this.loadingInstance = ElLoading.service({
fullscreen: true,
text: 'Loading...',
background: 'rgba(0, 0, 0, 0.5)'
});
}
}
}

// 隐藏加载状态
hideLoading() {
if (this.requestCount > 0) {
this.requestCount--;
if (this.requestCount === 0 && this.loadingInstance) {
this.loadingInstance.close();
this.loadingInstance = null;
}
}
}

// 处理响应数据
handleResponse(response) {
const { data, status } = response;

// 根据后端返回格式统一处理
if (status === 200) {
if (data.code === 200) {
return data.data;
} else {
ElMessage.error(data.message || 'Request failed');
return Promise.reject(data);
}
}

return data;
}

// 处理错误
handleError(error) {
const { response } = error;

if (response) {
const { status, data } = response;

switch (status) {
case 401:
// 未授权,跳转到登录页
ElMessage.error('Authentication failed, please login again');
localStorage.removeItem('token');
window.location.href = '/login';
break;
case 403:
ElMessage.error('Access denied');
break;
case 404:
ElMessage.error('Resource not found');
break;
case 500:
ElMessage.error('Server error');
break;
default:
ElMessage.error(data.message || 'Request error');
}
} else if (error.request) {
ElMessage.error('Network error');
} else {
ElMessage.error(error.message);
}

return Promise.reject(error);
}

// 请求方法
request(config) {
return this.instance.request(config);
}

get(url, params, config = {}) {
return this.instance.get(url, { params, ...config });
}

post(url, data, config = {}) {
return this.instance.post(url, data, config);
}

put(url, data, config = {}) {
return this.instance.put(url, data, config);
}

patch(url, data, config = {}) {
return this.instance.patch(url, data, config);
}

delete(url, config = {}) {
return this.instance.delete(url, config);
}
}

export default new Request();

创建 API 服务层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// src/api/index.js
import request from '@/utils/request';

// 统一导出所有 API
export * from './auth';
export * from './user';
export * from './post';
export * from './comment';

// src/api/auth.js
export const authAPI = {
login: (data) => request.post('/auth/login', data),
logout: () => request.post('/auth/logout'),
register: (data) => request.post('/auth/register', data),
getProfile: () => request.get('/auth/profile'),
refreshToken: () => request.post('/auth/refresh-token')
};

// src/api/user.js
export const userAPI = {
getUserList: (params) => request.get('/users', params),
getUserById: (id) => request.get(`/users/${id}`),
createUser: (data) => request.post('/users', data),
updateUser: (id, data) => request.put(`/users/${id}`, data),
deleteUser: (id) => request.delete(`/users/${id}`),
updatePassword: (data) => request.post('/users/password', data)
};

在组件中使用封装后的 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<template>
<div class="user-management">
<div class="search-bar">
<el-input v-model="searchKeyword" placeholder="Search users..." />
<el-button @click="fetchUsers">Search</el-button>
</div>

<el-table :data="userList" border>
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="Name" />
<el-table-column prop="email" label="Email" />
<el-table-column prop="status" label="Status" />
<el-table-column label="Actions" width="200">
<template #default="scope">
<el-button @click="editUser(scope.row)">Edit</el-button>
<el-button type="danger" @click="deleteUser(scope.row.id)">Delete</el-button>
</template>
</el-table-column>
</el-table>

<el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
@current-change="fetchUsers"
/>
</div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { userAPI } from '@/api';

const userList = ref([]);
const searchKeyword = ref('');
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);

const fetchUsers = async () => {
try {
const params = {
page: currentPage.value,
pageSize: pageSize.value
};

if (searchKeyword.value) {
params.keyword = searchKeyword.value;
}

const response = await userAPI.getUserList(params);
userList.value = response.list;
total.value = response.total;
} catch (error) {
console.error('Failed to fetch users:', error);
}
};

const deleteUser = async (id) => {
try {
await userAPI.deleteUser(id);
ElMessage.success('User deleted successfully');
fetchUsers(); // 重新加载列表
} catch (error) {
console.error('Failed to delete user:', error);
}
};

onMounted(() => {
fetchUsers();
});
</script>

网络知识科普

HTTP 协议基础

HTTP 协议概述

HTTP(HyperText Transfer Protocol)是超文本传输协议,是用于传输超媒体文档(如 HTML)的应用层协议。它基于 TCP/IP 协议,采用客户端-服务器模型。

HTTP 请求方法

方法 用途 安全性 幂等性
GET 获取资源 安全 幂等
POST 创建资源 不安全 不幂等
PUT 更新整个资源 不安全 幂等
PATCH 部分更新资源 不安全 不幂等
DELETE 删除资源 不安全 幂等
HEAD 获取响应头 安全 幂等
OPTIONS 获取资源支持的方法 安全 幂等

HTTP 状态码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
1xx: 信息性状态码
100 Continue - 继续处理请求
101 Switching Protocols - 协议切换

2xx: 成功状态码
200 OK - 请求成功
201 Created - 资源创建成功
204 No Content - 成功但无返回内容

3xx: 重定向状态码
301 Moved Permanently - 永久重定向
302 Found - 临时重定向
304 Not Modified - 资源未修改

4xx: 客户端错误状态码
400 Bad Request - 请求参数错误
401 Unauthorized - 未授权
403 Forbidden - 禁止访问
404 Not Found - 资源不存在
429 Too Many Requests - 请求过于频繁

5xx: 服务器错误状态码
500 Internal Server Error - 服务器内部错误
502 Bad Gateway - 网关错误
503 Service Unavailable - 服务不可用

RESTful API 设计规范

核心原则

  1. 资源导向:所有操作都是针对资源的

  2. 无状态性:每个请求都包含完整的信息

  3. 统一接口:使用标准的 HTTP 方法

  4. 可缓存性:支持缓存机制

  5. 分层系统:客户端不需要了解系统架构

URL 设计规范

1
2
3
4
5
6
7
8
9
10
11
# 正确的 URL 设计
GET /users # 获取用户列表
GET /users/123 # 获取指定用户
POST /users # 创建新用户
PUT /users/123 # 更新用户信息
DELETE /users/123 # 删除用户

# 错误的 URL 设计
GET /getUsers # 使用动词
GET /user/123 # 单数形式
POST /users/create # 冗余动词

查询参数设计

1
2
3
4
5
6
7
8
9
10
11
# 过滤
GET /users?role=admin&status=active

# 排序
GET /users?sort=name&order=asc

# 分页
GET /users?page=1&pageSize=20

# 字段筛选
GET /users?fields=id,name,email

跨域资源共享(CORS)

什么是 CORS

CORS(Cross-Origin Resource Sharing)是一种机制,允许浏览器向跨源服务器发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

CORS 工作原理

  1. 简单请求:直接发送请求,服务器返回 CORS 相关头

  2. 预检请求:复杂请求会先发送 OPTIONS 请求进行预检

常见 CORS 配置

1
2
3
4
5
6
7
8
9
10
11
12
// 服务器端 CORS 配置示例(Node.js/Express)
const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors({
origin: ['http://localhost:3000', 'https://example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400 // 预检缓存时间
}));

HTTP 缓存机制

缓存类型

  1. 强缓存:Expires 和 Cache-Control 头控制

  2. 协商缓存:Last-Modified/If-Modified-Since 和 ETag/If-None-Match

缓存控制头

1
2
3
4
5
6
7
# Cache-Control 指令
Cache-Control: public, max-age=3600, must-revalidate

# 响应头示例
Cache-Control: private, max-age=0, no-cache
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

安全性考虑

认证与授权

JWT 认证实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// JWT 工具类
class JWTUtils {
static setToken(token) {
localStorage.setItem('token', token);
// 也可以使用 HttpOnly Cookie
// document.cookie = `token=${token}; HttpOnly; Secure; SameSite=Strict`;
}

static getToken() {
return localStorage.getItem('token');
}

static removeToken() {
localStorage.removeItem('token');
}

static isTokenExpired(token) {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.exp * 1000 < Date.now();
} catch (error) {
return true;
}
}
}

// 请求拦截器中的认证处理
instance.interceptors.request.use(config => {
const token = JWTUtils.getToken();

if (token) {
if (JWTUtils.isTokenExpired(token)) {
// Token 过期,尝试刷新
return refreshToken()
.then(newToken => {
JWTUtils.setToken(newToken);
config.headers.Authorization = `Bearer ${newToken}`;
return config;
})
.catch(() => {
JWTUtils.removeToken();
window.location.href = '/login';
return Promise.reject(new Error('Token expired'));
});
}

config.headers.Authorization = `Bearer ${token}`;
}

return config;
});

CSRF 防护

CSRF 攻击原理

CSRF(Cross-Site Request Forgery)跨站请求伪造,攻击者利用用户已认证的身份执行非预期的操作。

防护措施

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 1. 使用 CSRF Token
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;

axios.post('/api/action', {
data: 'value'
}, {
headers: {
'X-CSRF-Token': csrfToken
}
});

// 2. 使用 SameSite Cookie
// Set-Cookie: sessionid=abc123; SameSite=Strict; Secure; HttpOnly

// 3. 验证 Origin/Referer 头
app.use((req, res, next) => {
const origin = req.headers.origin;
const allowedOrigins = ['https://example.com', 'http://localhost:3000'];

if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
next();
} else {
res.status(403).send('Forbidden');
}
});

XSS 防护

XSS 攻击类型

  1. 存储型 XSS:恶意脚本存储在服务器

  2. 反射型 XSS:恶意脚本通过 URL 参数注入

  3. DOM 型 XSS:客户端 JavaScript 处理不当

防护措施

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 输入验证
function sanitizeInput(input) {
return input
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}

// 2. 使用 Content Security Policy
// Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;

// 3. Vue 中的 v-text 和 v-bind
<template>
<!-- 安全的文本绑定 -->
<div v-text="userInput"></div>

<!-- 安全的属性绑定 -->
<div :title="sanitizedTitle"></div>
</template>

数据传输安全

HTTPS 配置

1
2
3
4
5
6
7
// 强制使用 HTTPS
if (process.env.NODE_ENV === 'production' && window.location.protocol !== 'https:') {
window.location.href = `https:${window.location.href.substring(window.location.protocol.length)}`;
}

// 安全的 cookie 设置
document.cookie = 'session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=86400';

性能优化

请求优化

请求合并与批处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class RequestBatcher {
constructor() {
this.queue = new Map();
this.timer = null;
}

// 添加请求到批处理队列
addRequest(url, params, callback) {
const key = `${url}-${JSON.stringify(params)}`;

if (!this.queue.has(key)) {
this.queue.set(key, { url, params, callbacks: [] });
}

this.queue.get(key).callbacks.push(callback);

this.scheduleBatch();
}

// 调度批处理
scheduleBatch() {
if (this.timer) return;

this.timer = setTimeout(() => {
this.processBatch();
}, 100); // 100ms 延迟合并请求
}

// 处理批处理请求
async processBatch() {
const batchRequests = Array.from(this.queue.values());

try {
// 发送批处理请求
const response = await axios.post('/api/batch', {
requests: batchRequests.map(req => ({
url: req.url,
params: req.params
}))
});

// 分发响应结果
response.data.forEach((result, index) => {
const request = batchRequests[index];
request.callbacks.forEach(callback => callback(result));
});
} catch (error) {
batchRequests.forEach(request => {
request.callbacks.forEach(callback => callback(null, error));
});
} finally {
this.queue.clear();
this.timer = null;
}
}
}

const batcher = new RequestBatcher();

请求缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class RequestCache {
constructor() {
this.cache = new Map();
this.maxAge = 5 * 60 * 1000; // 默认缓存 5 分钟
}

// 获取缓存数据
get(key) {
const item = this.cache.get(key);

if (!item) return null;

// 检查缓存是否过期
if (Date.now() - item.timestamp > this.maxAge) {
this.cache.delete(key);
return null;
}

return item.data;
}

// 设置缓存数据
set(key, data, maxAge = this.maxAge) {
this.cache.set(key, {
data,
timestamp: Date.now(),
maxAge
});

// 设置过期清理
setTimeout(() => {
this.cache.delete(key);
}, maxAge);
}

// 生成缓存 key
generateKey(url, params = {}) {
return `${url}?${new URLSearchParams(params).toString()}`;
}

// 缓存请求
async cachedRequest(url, params = {}, config = {}) {
const cacheKey = this.generateKey(url, params);
const cachedData = this.get(cacheKey);

if (cachedData && !config.forceRefresh) {
return cachedData;
}

try {
const response = await axios.get(url, { params, ...config });
this.set(cacheKey, response.data, config.maxAge);
return response.data;
} catch (error) {
// 如果有缓存,即使请求失败也返回缓存数据
if (cachedData) {
console.warn('Using cached data due to request failure');
return cachedData;
}
throw error;
}
}
}

const requestCache = new RequestCache();

响应优化

数据压缩

1
2
3
4
5
6
7
8
9
10
// 服务器端启用 Gzip 压缩
const compression = require('compression');
app.use(compression());

// 客户端请求压缩数据
axios.get('/api/data', {
headers: {
'Accept-Encoding': 'gzip, deflate'
}
});

图片优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 图片懒加载
const loadImage = (url) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
};

// 使用 WebP 格式
const getWebPImage = (url) => {
return new Promise((resolve) => {
const webpData = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
const img = new Image();

img.onload = () => {
if (img.width > 0 && img.height > 0) {
resolve(url.replace(/\.(jpg|jpeg|png)$/, '.webp'));
} else {
resolve(url);
}
};

img.onerror = () => resolve(url);
img.src = webpData;
});
};

连接优化

HTTP/2 支持

1
2
3
4
5
6
7
8
// 检查 HTTP/2 支持
if (window.location.protocol === 'https:') {
fetch('/api/check-http2')
.then(response => {
const isHttp2 = response.headers.get('x-http2') === 'h2';
console.log('HTTP/2 supported:', isHttp2);
});
}

连接池管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 自定义 Axios 适配器优化连接池
const http = require('http');
const https = require('https');

const httpAgent = new http.Agent({
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
freeSocketTimeout: 30000
});

const httpsAgent = new https.Agent({
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
freeSocketTimeout: 30000
});

const axiosInstance = axios.create({
httpAgent,
httpsAgent,
timeout: 10000
});

常见问题与解决方案

跨域问题

问题描述

前端应用与后端 API 不在同一个域名下,导致浏览器阻止请求。

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 1. 开发环境代理配置(Vue CLI)
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
},
cookieDomainRewrite: {
'localhost:3000': 'localhost:8080'
}
}
}
}
};

// 2. Vite 代理配置
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
};

请求超时处理

全局超时配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const axiosInstance = axios.create({
timeout: 10000 // 全局超时时间
});

// 单个请求超时配置
axios.get('/api/data', {
timeout: 5000 // 覆盖全局配置
});

// 超时重试机制
class RetryHandler {
static async requestWithRetry(config, retries = 3, delay = 1000) {
try {
return await axios(config);
} catch (error) {
if (retries > 0 && this.isRetryable(error)) {
await this.delay(delay);
return this.requestWithRetry(config, retries - 1, delay * 2);
}
throw error;
}
}

static isRetryable(error) {
return (
error.code === 'ECONNABORTED' ||
(error.response && error.response.status >= 500)
);
}

static delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}

重复请求处理

防止重复提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class RequestDeduplicator {
constructor() {
this.pendingRequests = new Map();
}

async request(config) {
const key = this.generateKey(config);

// 如果请求正在进行中,返回同一个 Promise
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key);
}

try {
const promise = axios(config).finally(() => {
this.pendingRequests.delete(key);
});

this.pendingRequests.set(key, promise);
return promise;
} catch (error) {
this.pendingRequests.delete(key);
throw error;
}
}

generateKey(config) {
const { method = 'get', url, params, data } = config;
return `${method}-${url}-${JSON.stringify(params)}-${JSON.stringify(data)}`;
}

cancelPendingRequests() {
this.pendingRequests.forEach((promise, key) => {
// 如果支持取消,取消请求
if (config.cancelToken) {
config.cancelToken.cancel('Request canceled by deduplicator');
}
this.pendingRequests.delete(key);
});
}
}

const deduplicator = new RequestDeduplicator();

错误处理最佳实践

统一错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 错误类型定义
class ApiError extends Error {
constructor(message, status = 500, code = 'UNKNOWN_ERROR', data = null) {
super(message);
this.name = 'ApiError';
this.status = status;
this.code = code;
this.data = data;
}
}

// 错误处理中间件
function errorHandler(error) {
if (error instanceof ApiError) {
return {
success: false,
error: {
message: error.message,
code: error.code,
status: error.status
}
};
}

if (axios.isAxiosError(error)) {
const response = error.response;

if (response) {
return {
success: false,
error: {
message: response.data.message || 'Request failed',
code: response.data.code || 'API_ERROR',
status: response.status
}
};
}

return {
success: false,
error: {
message: error.message || 'Network error',
code: 'NETWORK_ERROR',
status: 0
}
};
}

return {
success: false,
error: {
message: error.message || 'Unknown error',
code: 'UNKNOWN_ERROR',
status: 500
}
};
}

核心要点回顾

  1. 基础使用:掌握了 GET、POST、PUT、DELETE 等基本请求方法

  2. Vue 集成:学会了在 Vue 项目中优雅地使用 Axios

  3. 高级封装:构建了可复用的请求类和 API 服务层

  4. 网络知识:深入理解了 HTTP 协议、RESTful API、CORS 等概念

  5. 安全性:掌握了认证、授权、CSRF、XSS 等安全防护措施

  6. 性能优化:学会了请求合并、缓存、连接池等优化技巧

最佳实践建议

  1. 统一封装:始终对 Axios 进行二次封装,便于统一管理

  2. 错误处理:建立完善的错误处理机制,提升用户体验

  3. 安全意识:重视网络安全,保护用户数据和系统安全

  4. 性能优化:合理使用缓存、连接池等技术提升性能

  5. 代码规范:保持良好的代码组织和命名规范

技术发展趋势

随着 Web 技术的不断发展,我们还需要关注:

  • HTTP/3:基于 QUIC 协议的新一代 HTTP 协议

  • GraphQL:作为 RESTful API 的补充和替代方案

  • WebSocket:实时通信的重要技术

  • 边缘计算:在 CDN 边缘节点处理请求,提升性能


参考资源