在构建现代化的 React 单页应用(SPA)时,我们几乎不可避免地要与后端 API 进行交互。无论是获取用户数据、提交表单,还是流式传输内容,前端通常需要向服务器发起 HTTP 请求。然而,你可能会突然在控制台中看到类似这样的一行刺眼的红字:
> Access to XMLHttpRequest at ‘…‘ from origin ‘…‘ has been blocked by CORS policy…
这就是著名的 CORS(跨域资源共享) 错误。对于初学者来说,它可能像一个难以捉摸的幽灵;但对于经验丰富的开发者,理解并掌握 CORS 是构建健壮 Web 应用的必修课。在这篇文章中,我们将深入探讨 CORS 的工作原理,并使用 Axios 和 Fetch 两种主流方式,演示如何在 React 环境下优雅地解决跨域问题。我们将不仅仅停留在“让它跑通”的层面,更会深入到底层机制和最佳实践。
目录
CORS 的工作原理:不仅仅是“报错”
在动手写代码之前,让我们先拆解一下 CORS 到底是什么。简单来说,CORS 是一种基于 HTTP 头的机制,它允许服务器向浏览器明确指示:除了它自己(同源)之外,哪些其他的源(域名、协议或端口)有权限访问它的资源。
这其实是浏览器的一种安全策略,而不是服务器本身的限制。试想一下,如果没有 CORS,当你登录银行网站后,恶意网站可以在你不知情的情况下通过 AJAX 请求向银行 API 发送指令(因为浏览器会自动携带 Cookie),这后果不堪设想。CORS 的存在就是为了防止这种“跨站读取”的风险。
同源策略 vs 跨域请求
“同源”指的是三个条件完全相同:协议、域名和端口。例如,INLINECODE16dc295c 和 INLINECODEa7a98cf0 虽然都是 localhost,但端口不同,因此属于跨域。当我们的 React 应用运行在 INLINECODE2d6e88a5 而试图去访问 INLINECODE75c5289c 的 API 时,浏览器就会介入。
简单请求与预检请求
CORS 将请求分为两类:简单请求和非简单请求(Preflight,预检请求)。理解这一点对于调试至关重要。
- 简单请求:只要满足特定条件(如使用 GET、HEAD 或 POST 方法,且 Content-Type 限定为 INLINECODEbe9d6d90、INLINECODE811c0e3d 或 INLINECODE4e56ac2d),浏览器会直接发送请求,并在响应头中检查 INLINECODEaf38a7c2。如果不允许,浏览器才会抛出错误。
- 预检请求:如果我们使用了 PUT、DELETE 方法,或者设置了自定义的请求头(如 INLINECODEe18ed83d 或 INLINECODEb4d5cdc1),浏览器就会先发送一个 OPTIONS 请求。这就好比在正式进入房间前先敲门询问:“我打算带着这些数据和这个方法进来,你允许吗?” 只有当服务器返回肯定的响应头后,浏览器才会发送实际的业务请求。
准备工作:创建 React 环境
为了确保我们步调一致,让我们从零开始搭建一个演示环境。我们将创建一个组件,用于测试不同的跨域场景。
首先,通过终端创建一个新的 React 项目:
# 使用 create-react-app 快速搭建
npx create-react-app cors-demo-app
# 进入目录
cd cors-demo-app
# 启动开发服务器
npm start
此时,你的应用应该运行在 http://localhost:3000。为了模拟跨域,假设我们的 API 服务器位于另一个域。
方案一:使用 Axios 处理 CORS(推荐)
Axios 是目前 React 社区中最流行的 HTTP 客户端库。它基于 Promise 构建,不仅语法简洁,而且在处理复杂数据、请求拦截和响应转换方面表现出色。最重要的是,它在处理 CORS 时非常智能。
1. 安装 Axios
让我们先将 Axios 添加到项目中:
npm install axios
2. 实现 GET 请求与身份验证
在真实的业务场景中,API 通常受到保护。我们需要在请求头中携带 Bearer Token(令牌)。此外,如果涉及到服务器验证用户的 Cookie,我们还需要配置 withCredentials。
下面是一个完整的组件示例。请注意代码中的注释,它们解释了每一个关键步骤。
src/AxiosRequest.js
import React, { useState, useEffect } from ‘react‘;
import axios from ‘axios‘;
const AxiosRequest = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
// 模拟从本地存储获取的 JWT Token
const token = ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...‘;
useEffect(() => {
// 定义一个异步函数来获取数据
const fetchData = async () => {
try {
// 使用 axios 发起 GET 请求
const response = await axios.get(‘https://jsonplaceholder.typicode.com/posts/1‘, {
// headers:配置请求头
headers: {
// 告诉服务器我们接受 JSON 格式
‘Accept‘: ‘application/json‘,
// 发送 JSON 格式数据
‘Content-Type‘: ‘application/json‘,
// 携带 Bearer Token 进行身份验证
// 注意:这会触发非简单请求,导致浏览器先发送 OPTIONS 预检
‘Authorization‘: `Bearer ${token}`
},
// withCredentials: true 表示发送请求时携带 Cookie
// 这要求后端必须设置响应头:Access-Control-Allow-Credentials: true
// 且 Access-Control-Allow-Origin 不能为 *,必须是具体的源
withCredentials: true
});
console.log(‘服务器响应数据:‘, response.data);
setData(response.data);
setError(null);
} catch (err) {
// 处理错误:可能是网络错误,也可能是 CORS 拦截
console.error(‘CORS 或网络请求失败:‘, err.message);
setError(`请求失败: ${err.message}`);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
return (
Axios CORS 示例
{loading && 加载中...
}
{error && {error}
}
{data && (
标题: {data.title}
内容: {data.body}
)}
);
};
export default AxiosRequest;
3. 深入理解 Axios 的 CORS 行为
在这个例子中,我们并没有显式地告诉浏览器“这是一个跨域请求”。Axios 的强大之处在于它会根据请求的配置(如添加了自定义 Header Authorization)自动触发浏览器的 CORS 预检机制。
如果后端配置正确(例如返回了 Access-Control-Allow-Origin: http://localhost:3000),请求就会成功。
方案二:使用 Fetch API 处理 CORS
虽然 Axios 很棒,但作为一个现代前端开发者,我们也必须掌握原生的 Fetch API。Fetch 是浏览器内置的,不需要安装额外的包,而且随着 React Hooks 的普及,使用 Fetch + async/await 写出的代码也非常优雅。
1. 为什么 Fetch 需要更多配置?
与 Axios 不同,Fetch 是底层的 API。当我们需要发送跨域请求时,特别是那些需要携带 Cookie 或凭证的请求,我们必须显式地设置 INLINECODE371a257a 和 INLINECODE34d395ec 选项。如果不设置,浏览器默认不会在跨域请求中发送 Cookie,导致身份验证失败。
2. 实现带凭证的 Fetch 请求
让我们用 Fetch 重写上面的逻辑。注意 INLINECODEc7acb4fe 和 INLINECODEaa872f08 的使用。
src/FetchRequest.js
import React, { useState, useEffect } from ‘react‘; const FetchRequest = () => { const [data, setData] = useState(null); const [error, setError] = useState(null); const token = ‘your_fetch_token_here‘; useEffect(() => { const fetchData = async () => { try { console.log(‘正在使用 Fetch 发起 CORS 请求...‘); const response = await fetch(‘https://jsonplaceholder.typicode.com/posts/1‘, { method: ‘GET‘, // 或者 POST, PUT, DELETE // mode: ‘cors‘ 明确告诉浏览器这是一个跨域请求 // 虽然这通常是默认值,但明确写出来可以提高代码可读性 mode: ‘cors‘, // credentials: ‘include‘ 是关键 // 它指示浏览器在跨域请求中包含 Cookie 和 Authorization 头 // 对应的值还有 ‘same-origin‘ (默认) 和 ‘omit‘ credentials: ‘include‘, headers: { ‘Content-Type‘: ‘application/json‘, ‘Authorization‘: `Bearer ${token}`, }, }); // Fetch 不同于 Axios,只要收到 HTTP 响应(即使是 404 或 500), // Promise 都会 resolve。我们需要手动检查 ok 状态。 if (!response.ok) { throw new Error(`HTTP 错误! 状态码: ${response.status}`); } const result = await response.json(); console.log(‘Fetch 获取的数据:‘, result); setData(result); } catch (err) { console.error(‘Fetch 错误:‘, err); setError(err.message); } }; fetchData(); }, []); return (Fetch API CORS 示例
{error &&错误详情: {error}
} {data &&{JSON.stringify(data, null, 2)}}
);
};export default FetchRequest;
常见的 CORS 错误与解决方案
即使你按照上述步骤操作,在开发过程中依然可能会遇到各种坑。这里我们总结了一些最常见的错误及其应对策略。
1.
No ‘Access-Control-Allow-Origin‘ header is present现象:控制台报错,说响应头中缺少
Access-Control-Allow-Origin。
原因:这是最经典的问题。服务器没有允许你的域名访问。
解决方案:
- 如果你控制后端:你需要修改服务器代码。例如在 Node.js (Express) 中,你需要使用
cors中间件:
// 服务端代码示例
const cors = require(‘cors‘);
const app = express();
// 允许所有源(开发环境可以用,生产环境慎用)
app.use(cors());
// 或者配置特定源
app.use(cors({
origin: ‘http://localhost:3000‘,
credentials: true
}));
2. 开发环境代理方案(Create React App / Vite)
在开发中,我们可以利用前端开发服务器的能力来绕过浏览器的同源策略。我们可以让开发服务器(例如 Webpack Dev Server 运行在 3000 端口)去代理请求到后端 API(5000 端口)。因为服务器对服务器的请求不受浏览器 CORS 限制。
配置方法:在项目根目录创建 package.json 或对应的配置文件。
对于 Create React App,只需在 package.json 中添加一行:
"proxy": "https://api.backend.com",
或者在 src/setupProxy.js 中使用更灵活的配置(推荐):
// src/setupProxy.js
const { createProxyMiddleware } = require(‘http-proxy-middleware‘);
module.exports = function(app) {
app.use(
‘/api‘,
createProxyMiddleware({
target: ‘https://api.backend.com‘, // 后端真实地址
changeOrigin: true,
})
);
};
配置完成后,你在 React 代码中只需请求 INLINECODE14b5b32d,Webpack 会自动将其转发给 INLINECODEc81e7872。
3. The value of ‘Access-Control-Allow-Origin‘ cannot be ‘*‘
现象:当你设置了 INLINECODEfccb63c3 (Axios) 或 INLINECODEcb57c907 (Fetch) 时,服务器如果返回 Access-Control-Allow-Origin: *,浏览器会报错并拦截响应。
原因:为了安全,当请求携带凭证时,服务器必须指定具体的源(Origin),不能使用通配符 *。
解决方案:后端需要动态获取请求头中的 INLINECODE53bd4a7d,并将其原样返回在 INLINECODEefdbc351 中。
性能优化与最佳实践
处理 CORS 不仅仅是解决报错,良好的架构还能提升应用的性能和安全性。
- 减少预检请求:预检请求会增加一次 HTTP 往返,这会显著增加延迟。如果你的 API 是幂等的(可以安全地重复发送),可以在服务器端配置
Access-Control-Max-Age头。这告诉浏览器:“在接下来的 N 秒内,针对这个 API 的预检请求结果都是有效的,不需要再问我了”。
Access-Control-Max-Age: 86400 // 24小时
- 合理使用 INLINECODE611f68f3:只有在确实需要携带身份信息(如 Cookie、HTTP Auth)时才开启 INLINECODE0e7e0e58。这可以减少不必要的复杂性,避免浏览器因为安全策略过于严格而阻止响应。
- 错误处理:在生产环境中,务必捕获 CORS 错误并给用户友好的提示。不要只打印
console.error,可以在 UI 上显示“网络连接似乎有点问题,请检查网络或稍后再试”。
总结
在 React 应用中处理 CORS 跨域问题是每个前端开发者的必经之路。我们今天不仅学习了如何使用 Axios 和 Fetch 发起跨域请求,还深入探讨了浏览器的安全机制、预检流程以及“源”的概念。
关键要点回顾:
- CORS 是浏览器的安全机制,用于保护用户数据。
- 简单请求直接发送,带有自定义 Header 或特殊方法的请求会先发送 OPTIONS 预检。
- Axios 自动处理了大部分细节,而 Fetch 需要我们显式设置 INLINECODE506a7597 和 INLINECODEf3e06e08。
- 开发环境下,利用 Proxy 是绕过 CORS 限制的最快方法。
- 生产环境下,后端必须正确配置 INLINECODE523247c6 和 INLINECODE99b9df1e。
掌握了这些知识,你就可以自信地构建既能与外部世界安全通信,又能提供流畅用户体验的 React 应用了!