想象一下你在淘宝或者京东上买东西,手指轻轻一点“加入购物车”,页面上立刻跳出一个提示框告诉你“添加成功”,而且不需要刷新整个网页,整个界面的价格和商品列表就像变魔术一样瞬间更新了。这种丝滑的体验,其实背后有一套非常经典的技术逻辑在支撑。
咱们今天就来聊聊这个“魔法”是如何实现的。核心就在于AJAX(异步JavaScript和XML)技术,它是让网页“活”起来的关键。而如果你使用的是现代前端框架(比如Vue、React),配合 Axios 这个HTTP请求库,再加上拦截器这个强大的工具,就能轻松搞定跨域问题和加载状态的反馈。
1. 为什么网页不刷新还能更新数据?
传统的网页交互是“请求-响应”模式:你点一下按钮,浏览器向服务器要一整张新网页,服务器给你一整张新网页,浏览器把旧的擦掉,把新的画上去。这就导致页面闪烁,体验不好。
现在的做法是:
- 前端(你的浏览器)捕捉到点击事件。
- AJAX:悄悄地(不刷新页面)向后端服务器发送一条消息:“嘿,我要把ID为99的商品加到购物车。”
- 后端收到消息,更新数据库,然后回复前端:“好嘞,操作成功,现在总价是100元。”
- 前端收到回复,只把价格那个数字改了,或者弹个窗,剩下的页面纹丝不动。
这就叫“无刷新更新”。
2. 核心工具:Axios 拦截器
在 Vue 或 React 项目中,我们通常用 Axios 来发请求。但如果你直接写 axios.post(),每次都要写一堆 loading = true、try...catch 代码,那简直是灾难。这时候,拦截器 就派上用场了。
拦截器就像是安检员。它在你发请求之前先检查一遍,在你收到响应之后也检查一遍。
2.1 请求拦截器:搞定“加载状态”
当用户点击按钮时,最怕的就是点了没反应。这时候,我们需要一个“加载中”的转圈圈或者遮罩层。
我们可以利用请求拦截器,在请求发出的瞬间,自动把 loading 状态设为 true。
// 这段代码通常放在你的 axios 配置文件中,比如 request.js 或 http.js
import axios from 'axios';
// 创建实例
const service = axios.create({
baseURL: 'http://your-api-server.com', // 你的后端接口地址
timeout: 5000,
});
// 【关键点1】请求拦截器
service.interceptors.request.use(
config => {
// 在这里,你可以把全局的 loading 状态设为 true
// 比如:this.loading = true (如果是 Vue 组件内) 或者 store.dispatch('setLoading', true)
console.log('请求已发出,准备开始加载数据...');
// 如果请求需要携带 Token,也可以在这里加
// config.headers.Authorization = `Bearer ${token}`;
return config;
},
error => {
console.error('请求发送失败:', error);
return Promise.reject(error);
}
);
export default service;
2.2 响应拦截器:搞定“错误处理”与“跨域反馈”
当后端发回数据后,拦截器会再次拦截。如果后端报错了(比如跨域报错,或者数据格式不对),我们可以在拦截器里统一处理,不让前端代码崩溃。
关于跨域,浏览器有同源策略限制,Axios 本身无法绕过浏览器的安全限制,但拦截器可以帮助我们优雅地处理跨域产生的错误信息。
// 【关键点2】响应拦截器
service.interceptors.response.use(
response => {
// 2xx 范围内的状态码都会触发该函数
console.log('收到后端响应:', response.data);
// 可以在这里统一提取 data,省得每次都要 response.data.xxx
return response.data;
},
error => {
console.error('请求响应错误:', error);
// 这里专门处理跨域问题
if (error.response) {
// 服务器返回了状态码,但状态码不在 2xx 范围内
switch (error.response.status) {
case 401:
console.log('未授权,请登录');
break;
case 403:
console.log('拒绝访问');
break;
case 404:
console.log('请求地址出错');
break;
case 500:
console.log('服务器错误');
break;
default:
console.log(`连接错误,状态码${error.response.status}`);
}
} else if (error.request) {
// 请求已发出,但没有收到响应(可能是跨域,或者后端挂了)
console.log('请求已发出,但无响应(可能是跨域问题或服务器离线)');
console.error('跨域相关错误详情:', error.message);
} else {
// 设置请求时发生错误
console.log('请求设置错误:', error.message);
}
return Promise.reject(error);
}
);
3. 实战演示:Vue 3 组件中的完整应用
光有拦截器还不够,我们得把它用到具体的功能里。假设我们在做一个商品详情页,有一个“加入购物车”的按钮。
我们需要一个 loading 变量来控制 UI 的显示,并且使用刚才配置好的 Axios 实例。
<template>
<div class="product-page">
<h1>超级飞侠模型 (ID: 8866)</h1>
<p class="price">当前价格: ¥<span id="price-tag">{{ currentPrice }}</span></p>
<!-- 加载状态遮罩层 -->
<div v-if="isLoading" class="loading-overlay">
<div class="spinner"></div>
<p>正在计算价格...</p>
</div>
<!-- 加入购物车按钮 -->
<button
@click="addToCart"
:disabled="isLoading"
class="add-btn">
{{ isLoading ? '处理中...' : '加入购物车' }}
</button>
<!-- 错误提示 -->
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import request from './request'; // 引入上面配置好的 axios 实例
// 数据状态
const currentPrice = ref(99.00);
const isLoading = ref(false);
const errorMessage = ref('');
// 模拟后端接口地址(实际开发中是真实的)
const API_BASE = 'http://localhost:3000/api';
// 核心方法:加入购物车
const addToCart = async () => {
// 1. 重置错误信息
errorMessage.value = '';
// 2. 设置加载状态(此时请求拦截器会自动生效,虽然我们这里手动控制了UI,但拦截器里的 console.log 也会执行)
isLoading.value = true;
try {
// 3. 发送 AJAX 请求
// 我们使用的是刚才配置好的 request 实例
const res = await request.post(`${API_BASE}/cart/add`, {
productId: 8866,
quantity: 1
});
// 4. 请求成功,更新 UI
if (res.success) {
// 模拟价格变动:比如加购后价格减 5 元
currentPrice.value = res.newPrice;
alert(`成功!新价格已更新为 ${res.newPrice} 元`);
}
} catch (err) {
// 5. 请求失败,拦截器已经捕获了错误并打印在控制台,这里我们展示给用户看
errorMessage.value = '添加失败,请检查网络或跨域设置';
} finally {
// 6. 无论成功失败,都要关闭 loading
isLoading.value = false;
}
};
</script>
<style scoped>
/* 简单的样式,让效果更明显 */
.add-btn {
background-color: #ff5000;
color: white;
border: none;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: opacity 0.3s;
}
.add-btn:hover {
opacity: 0.9;
}
.add-btn:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.loading-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(255,255,255,0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
}
.error-message {
color: red;
margin-top: 10px;
}
</style>
4. 如何真正解决跨域问题?
你可能会问,我在代码里写了 localhost:3000,浏览器报错说跨域了怎么办?
重要提示: 拦截器只能管理前端逻辑,它不能直接修改浏览器的安全策略(CORS)。要解决跨域,我们需要在开发环境中配置代理(Proxy)。
如果你用的是 Vue CLI,在 vue.config.js 里加一行:
module.exports = {
devServer: {
proxy: {
'/api': { // 所有的 /api 开头的请求都会被代理
target: 'http://your-real-backend-server.com', // 真实的后端地址
changeOrigin: true, // 修改请求头中的 origin 为目标地址
pathRewrite: { '^/api': '' } // 重写路径,去掉 api 前缀
}
}
}
};
这样配置后,前端请求 http://localhost:8080/api/cart/add,实际上会被转发到 http://your-real-backend-server.com/cart/add,浏览器就认为这是同源的,跨域问题就完美解决了。
5. 总结一下体验流程
当你点击按钮时,整个流程是这样的:
- 视觉反馈:你看到按钮变成“处理中…”,或者出现一个遮罩层。这是通过 Vue 的
v-if="isLoading"控制的。 - AJAX 发送:Axios 请求拦截器被触发,打印日志,准备发送请求。
- 数据传输:如果配置了代理,请求顺利到达后端;如果没配置,拦截器会捕获 404/Network Error 错误。
- 后端处理:后端计算价格,更新数据库,返回 JSON 数据。
- 数据回传:Axios 响应拦截器收到数据,提取
newPrice。 - 页面更新:Vue 响应式系统检测到
currentPrice变了,自动把 DOM 里的数字刷新成新价格。
这就是为什么现代 Web 应用看起来像原生 App 一样流畅的原因。通过 Axios 拦截器,我们不仅解决了“怎么发请求”的技术问题,还顺便把用户体验(加载状态)和系统健壮性(错误处理)都给照顾到了。
