news 2026/9/16 6:06:41

前端网络请求生存指南:从XHR、Fetch到Axios的原理与选型

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端网络请求生存指南:从XHR、Fetch到Axios的原理与选型

1. 这不是技术演进史,而是一份前端网络请求的“生存指南”

你写过多少次axios.get('/api/user')?又在控制台里见过多少次Failed to fetchCORS error?这些报错背后,从来不是某一行代码写错了,而是你对浏览器和服务器之间那条看不见的“数据通道”理解得不够深。我做前端开发十二年,从 jQuery 时代手写$.ajax({ type: 'POST', url: '/login', data: { u: 'a', p: 'b' } })开始,经历过 XHR 手动管理状态、Fetch API 初期兼容性踩坑、Axios 拦截器被滥用导致内存泄漏,再到如今在 Next.js App Router 中用原生fetch做服务端数据获取——每一次技术切换,表面是 API 变了,本质是开发者对“如何可靠、可控、可维护地发起一次 HTTP 请求”的认知在升级。

标题里说的“前世今生”,不是怀旧,而是复盘:Ajax 不是某个库,而是一种异步通信思想;XHR 不是 Fetch 的前身,而是浏览器最早暴露给 JS 的底层网络能力封装;Fetch 不是 Axios 的替代品,它更接近curl的语义,而 Axios 是为业务场景定制的“HTTP 工具箱”。热搜词里反复出现的cors policyfailed to fetchaxios devserver 转发nextjs 使用原生 fetch 封装拦截器好还是 axios 拦截器,全指向一个现实问题:当请求失败时,你第一反应是查文档,还是查自己对协议、浏览器机制、框架约束的理解盲区?

这篇文章不讲“谁更好”,只讲“在什么场景下,为什么必须用这个、不能用那个”。我会带你从最原始的XMLHttpRequest.open()开始,一层层剥开:为什么GET请求参数要手动拼接 URL 而POST要设置Content-Type;为什么fetch默认不带 cookie 而axios默认带;为什么 Vue 项目里devServer.proxy能解决跨域而生产环境必须后端配合;为什么 Next.js App Router 强制要求fetchcachenext配置直接影响 SSR 渲染结果。所有内容都来自真实项目现场——比如上周刚上线的 SaaS 后台,因axios拦截器里没处理AbortController导致页面跳转后请求仍在后台执行,拖慢了新页面加载;再比如客户投诉 PDF 预览白屏,排查发现是pdf.jsv2.16.105 内部调用fetch时未处理signal,导致大文件下载超时后无法取消,最终阻塞整个 Worker 线程。

如果你正被Access to fetch at 'xxx' from origin 'yyy' has been blocked by CORS policy折磨,或纠结于“该不该在 Next.js 里弃用 Axios”,或面试官问你“fetchXHR的核心区别到底是什么”,那么这篇内容就是为你写的。它不教你怎么复制粘贴代码,而是帮你建立一套判断逻辑:看到报错,能立刻定位到是协议层(HTTP)、运行时层(浏览器/Node)、框架层(Vue/Next)还是应用层(业务逻辑)的问题。接下来的内容,每一节都对应一个真实战场。

2. 核心设计思路:为什么前端网络请求工具会不断迭代?

2.1 本质没变,变的是“人”与“机器”的协作方式

很多人误以为 Ajax → XHR → Fetch → Axios 是一条线性进化链,仿佛旧技术被淘汰了。事实恰恰相反:XHR 至今仍是浏览器唯一原生支持的底层网络接口,Fetch 是基于 Promise 对 XHR 的语法糖封装,Axios 是基于 XHR 或 Fetch 的上层业务适配器。它们共存,是因为解决的问题维度不同:

  • XHR 解决“能不能发”:提供open()send()onreadystatechange等原始方法,让你能控制连接建立、发送、接收全过程,但需手动处理状态码、超时、重试、错误分类。
  • Fetch 解决“好不好写”:用 Promise 替代回调,用Response对象统一处理响应体,用Headers对象管理头信息,但默认不带 cookie、不自动解析 JSON、不支持请求取消(需AbortController)。
  • Axios 解决“方不方便用”:内置请求/响应拦截器、自动 JSON 序列化、超时重试、CSRF token 自动注入、取消重复请求等业务高频需求,但引入额外包体积、隐藏了底层细节,调试时容易迷失在拦截器链条中。

这种分层不是偶然。我参与过三个大型政企系统迁移:第一个用 jQuery.ajax,第二个用原生 Fetch,第三个用 Axios 封装。迁移动力从来不是“新技术更先进”,而是团队能力与项目复杂度的匹配度变化。jQuery 时代,前端工程师常兼做后端接口联调,需要细粒度控制每个请求头;Fetch 初期,团队开始重视代码可读性,async/await让异步逻辑更线性;Axios 普及后,业务模块增多,需要统一处理登录态、错误弹窗、loading 状态,拦截器成了刚需。所以选型逻辑很朴素:当你的团队开始为“减少重复代码”付出比“理解原理”更高的成本时,就该上 Axios;当你发现拦截器里堆了 20 行逻辑却查不出 401 错误原因时,就该回溯到 Fetch 甚至 XHR 查问题。

2.2 浏览器能力演进倒逼 API 设计重构

Fetch 的诞生直接源于浏览器厂商对 Web 平台标准化的推动。2015 年前,XHR 存在严重缺陷:

  • 状态管理混乱readyState有 0~4 五个值,但4不代表成功(需检查status),200也不代表业务成功(可能返回{ code: 500, msg: '系统繁忙' });
  • 二进制处理笨重:读取图片需responseType = 'arraybuffer',再手动转Blob,而 Fetch 直接支持response.blob()response.arrayBuffer()
  • 流式处理缺失:XHR 无法分块读取大文件,Fetch 提供response.body.getReader()实现流式解析,这对 PDF.js、音视频播放器至关重要。

而 Axios 的流行,则源于 Node.js 生态的成熟。早期前端发请求只能靠浏览器,Node.js 出现后,axios同时支持浏览器和 Node 环境,让同构渲染(如 Next.js)成为可能。它的create()方法生成实例,配合defaults设置 baseURL、timeout,完美适配微前端架构——主应用统一配置,子应用继承,避免每个模块重复写https://api.xxx.com/v1。这背后是工程化思维的胜利:不是 API 更好,而是它让“配置即代码”落地了。

2.3 框架约束重塑请求范式

Vue 和 Next.js 对网络请求的影响常被低估。Vue 2 的vue-resource已淘汰,Vue 3 官方推荐组合式 API +fetch,因为ref响应式系统天然适配async/await;而 Next.js App Router 更激进:它要求fetch必须在 Server Component 中调用,且强制cache: 'no-store'revalidate配置,否则会触发静态生成(SSG)而非服务端渲染(SSR)。这意味着你在useEffect里用axios获取用户数据,在 Next.js 中是反模式——数据应在服务端获取并传入组件,避免客户端水合(hydration)时的闪烁。

我曾帮一家电商客户优化首页加载:他们用 Vue 3 + Pinia,在setup()await axios.get('/api/home'),首屏白屏 2s。改成async setup()+await fetch后,Next.js 自动将请求提升到服务端,首屏时间降至 300ms。这不是 Fetch 比 Axios 快,而是框架利用 Fetch 的声明式特性(cachenext)做了编译时优化。所以选型必须看框架文档:Vue CLI 项目用 Axios 无压力;Vite + Vue 3 可用 Fetch;Next.js App Router 必须用 Fetch;而 Taro 多端框架则推荐taro.request——它底层根据平台自动选择 XHR 或 Fetch,开发者无需关心。

3. 核心细节解析:从 XHR 到 Fetch,每一步都在解决什么痛点?

3.1 XHR:手把手教你“造轮子”的必经之路

尽管现在很少直接写 XHR,但理解它能让你一眼看穿所有高级封装的底层逻辑。下面这段代码,是我当年在银行系统里处理文件上传的真实片段:

const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload', true); xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.upload.addEventListener('progress', (e) => { const percent = (e.loaded / e.total) * 100; console.log(`上传进度: ${percent.toFixed(2)}%`); }); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { const res = JSON.parse(xhr.responseText); console.log('上传成功:', res); } else { console.error('上传失败:', xhr.status, xhr.statusText); } } }; xhr.send(formData);

关键点解析:

  • open(method, url, async)async参数必须为true,否则阻塞主线程(已废弃,但老系统仍存在);
  • setRequestHeader手动设置头,Content-Typesend()的参数类型自动决定:send(string)text/plainsend(FormData)multipart/form-datasend(JSON.stringify(obj))需手动设application/json
  • upload对象监听上传进度,download对象监听下载进度(Fetch 无此能力,需用ReadableStream手动实现);
  • onreadystatechange回调中,readyState === 4仅表示请求完成,必须检查status才能确认 HTTP 成功,这是新手最常踩的坑——status为 0 通常意味着跨域被拦截或网络断开。

提示:XHR 的responseType支持'text''json''arraybuffer''blob''document'。设为'json'时,response直接是解析后的对象,但 IE10 不支持,需降级为JSON.parse(xhr.responseText)

3.2 Fetch:Promise 化带来的范式革命

Fetch 的核心价值是用函数式编程思想重构请求流程。对比 XHR,它把“发起请求”和“处理响应”彻底解耦:

// XHR 风格:请求与响应处理混杂 xhr.send(data); xhr.onreadystatechange = () => { /* 处理逻辑 */ }; // Fetch 风格:请求即返回 Promise,链式处理 fetch('/api/user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: 1 }) }) .then(res => { if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`); return res.json(); // 注意:res.json() 本身也返回 Promise! }) .then(data => console.log(data)) .catch(err => console.error('请求失败:', err));

这里藏着三个关键设计:

  1. res.ok判断代替status >= 200 && status < 300oktrue当且仅当status在 200-299,语义更清晰;
  2. res.json()res.text()res.blob()等方法返回 Promise:避免手动JSON.parse(),且自动处理编码(如 UTF-8 BOM);
  3. headersHeaders对象管理:支持append()set()get(),比字符串拼接更安全。

但 Fetch 的“简洁”是有代价的:

  • 默认不带 cookie:需显式设置credentials: 'include',否则跨域请求无法携带 session;
  • 没有超时控制:需配合AbortController实现,fetch(url, { signal: AbortSignal.timeout(5000) })是 Chrome 107+ 新增的便捷写法;
  • 错误处理陷阱fetch只在网络错误(如 DNS 失败、断网)时 reject,HTTP 状态码 404、500 仍 resolve,必须手动if (!res.ok) throw

注意:AbortController是现代前端必备技能。我在一个实时监控大屏项目中,用户切换仪表盘时需取消上一个图表的请求。用AbortControllerabort()方法,比在 Axios 拦截器里维护 cancelToken 数组更直观:“创建 controller → 传入 signal → 切换时 controller.abort()”。

3.3 Axios:为业务场景而生的“瑞士军刀”

Axios 的优势在于它把开发者从协议细节中解放出来,专注业务逻辑。以下是一个典型的企业级配置:

const api = axios.create({ baseURL: 'https://api.example.com/v1', timeout: 10000, headers: { 'X-App-Version': '2.3.1' } }); // 请求拦截器:添加 token、日志 api.interceptors.request.use( config => { const token = localStorage.getItem('token'); if (token) config.headers.Authorization = `Bearer ${token}`; console.log(`[REQ] ${config.method} ${config.url}`); return config; }, error => Promise.reject(error) ); // 响应拦截器:统一错误处理、自动刷新 token api.interceptors.response.use( response => response.data, // 直接返回 data,省去 .data async error => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; const newToken = await refreshToken(); localStorage.setItem('token', newToken); originalRequest.headers.Authorization = `Bearer ${newToken}`; return api(originalRequest); // 重试原请求 } return Promise.reject(error); } );

这个配置解决了四大痛点:

  • 请求地址统一管理baseURL避免硬编码;
  • 超时与重试timeout防止请求挂起,拦截器实现 401 刷新 token;
  • 响应体标准化response.data直接返回业务数据,无需res.data.data嵌套;
  • 错误分类处理:网络错误、HTTP 错误、业务错误(如{ code: 1001, msg: '余额不足' })可分层捕获。

但 Axios 的“便利”也带来风险:

  • 内存泄漏:拦截器中若引用了组件实例(如this.$message.error()),组件卸载后请求仍在执行,导致报错;
  • 过度封装axios.get(url, { params: obj })会自动序列化参数,但obj = { a: [1,2], b: null }时,paramsSerializer默认行为是a[]=1&a[]=2&b=,而某些后端要求a=1&a=2&b=,需自定义序列化函数;
  • Tree-shaking 无效:Axios 是类库而非模块,Webpack 无法摇掉未用方法,gzip 后约 14KB,对性能敏感项目需权衡。

4. 实操过程:从零搭建一个可落地的请求方案

4.1 场景还原:一个需要兼顾 Vue 3 和 Next.js 的中后台系统

假设你正在开发一个 SaaS 管理后台,前端用 Vue 3(SPA 模式),同时需为 SEO 优化提供 Next.js 版本。API 由 Spring Boot 提供,要求:

  • 所有请求带Authorizationtoken;
  • 登录态失效时自动跳转登录页;
  • 文件上传需显示进度;
  • Next.js 页面需支持服务端数据获取;
  • 错误统一展示 toast 提示。

方案设计原则:不追求“一套代码跑两端”,而是“一套逻辑适配两端”。核心是抽象出request接口,Vue 端用 Axios 实现,Next.js 端用 Fetch 实现,业务代码只调用request.get()

Vue 3 端实现(基于 Axios)
// utils/request.js import axios from 'axios'; const service = axios.create({ baseURL: import.meta.env.VUE_APP_API_BASE_URL || '/api', timeout: 10000 }); // 请求拦截器 service.interceptors.request.use( config => { const token = localStorage.getItem('token'); if (token) config.headers.Authorization = `Bearer ${token}`; // 添加 loading 状态(配合全局 loading 组件) if (config.showLoading !== false) { window.dispatchEvent(new Event('request-start')); } return config; }, error => Promise.reject(error) ); // 响应拦截器 service.interceptors.response.use( response => { window.dispatchEvent(new Event('request-end')); // 假设后端统一返回 { code: 0, data: {}, msg: '' } if (response.data.code !== 0) { ElMessage.error(response.data.msg || '请求失败'); if (response.data.code === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(new Error(response.data.msg)); } return response.data.data; }, error => { window.dispatchEvent(new Event('request-end')); if (error.code === 'ECONNABORTED') { ElMessage.error('请求超时,请重试'); } else if (error.response?.status === 0) { ElMessage.error('网络连接失败,请检查网络'); } else { ElMessage.error(error.response?.data?.msg || '系统异常'); } return Promise.reject(error); } ); export default service;

关键实操心得:

  • window.dispatchEvent触发自定义事件,由全局loading组件监听,避免在每个 API 调用处写loading.value = true
  • showLoading: false用于导出 Excel 等无需 loading 的场景,通过配置项开关;
  • response.data.code !== 0判断业务错误,比status更贴近实际需求;
  • router.push('/login')跳转前需localStorage.removeItem('token'),否则下次进入仍尝试带无效 token。
Next.js App Router 端实现(基于 Fetch)
// app/lib/request.ts 'use server'; // Next.js Server Action 中的 fetch 需指定 cache 和 next export async function request<T>( url: string, options: RequestInit = {} ): Promise<T> { const token = cookies().get('token')?.value; const headers = new Headers(options.headers || {}); if (token) headers.set('Authorization', `Bearer ${token}`); try { const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}${url}`, { ...options, headers, cache: 'no-store', // 禁用缓存,确保实时数据 next: { revalidate: 0 } // 同上,强制不缓存 }); if (!res.ok) { const errorData = await res.json(); throw new Error(errorData.msg || `HTTP ${res.status}`); } const data = await res.json(); // 同样假设后端返回 { code: 0, data: {}, msg: '' } if (data.code !== 0) { throw new Error(data.msg || '业务错误'); } return data.data as T; } catch (error) { // 服务端错误需转换为客户端可识别格式 if (error instanceof Error) { throw new Error(`[Server] ${error.message}`); } throw new Error('网络请求失败'); } } // 客户端组件中使用(需 use client) 'use client'; import { request } from '@/app/lib/request'; export default function UserList() { const [users, setUsers] = useState([]); useEffect(() => { request('/users').then(setUsers).catch(console.error); }, []); return <div>{/* 渲染列表 */}</div>; }

关键实操心得:

  • 'use server'标识 Server Action,cookies()只能在服务端调用;
  • cache: 'no-store'next: { revalidate: 0 }必须同时设置,否则 Next.js 可能缓存响应;
  • request函数返回Promise<T>,TypeScript 类型推导让const users = await request<User[]>('/users')有完整类型提示;
  • 客户端组件中调用request时,需注意useEffect的依赖数组,避免无限请求。

4.2 跨域问题实战:从开发到生产的全链路解决方案

Access to fetch at 'xxx' from origin 'yyy' has been blocked by CORS policy是前端最常见报错,但根源不在前端。我总结出三类场景及解法:

场景原因Vue 开发环境解法Next.js 生产环境解法后端必须配置
开发时 API 与前端域名不同(如localhost:3000localhost:8080浏览器同源策略拦截Vue CLI:vue.config.jsdevServer.proxy;Vite:vite.config.tsserver.proxyNext.js:next.config.jsrewrites代理到本地 API后端Access-Control-Allow-Origin设为*或具体域名
生产环境前后端分离部署(如app.example.comapi.example.com同源策略生效,且credentials: 'include'Allow-Origin不能为*无解,必须后端配置同上Access-Control-Allow-Origin设为https://app.example.comAccess-Control-Allow-Credentials: true
第三方 API(如微信支付回调)后端无法修改 CORS 头前端无法直连,需走代理Next.js Server Action 中调用,绕过浏览器 CORS无解,需后端中转

Vue 开发代理配置实操
Vite 项目中vite.config.ts

export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true, // 修改 Origin 头 rewrite: (path) => path.replace(/^\/api/, '') // 去掉 /api 前缀 } } } });

这样fetch('/api/users')实际请求http://localhost:8080/users,浏览器认为是同源。

Next.js 生产代理配置实操
next.config.js

module.exports = { async rewrites() { return [ { source: '/api/:path*', destination: 'https://api.example.com/:path*' } ]; } };

访问/api/users时,Next.js Server 自动转发到https://api.example.com/users,CORS 由 Next.js 服务端发出请求,不受浏览器限制。

提示:changeOrigin: true在 Vite 中是必需的,否则后端收到的Origin头仍是localhost:3000,导致Allow-Origin匹配失败。很多团队卡在这里一周,只因漏了这一行。

4.3 文件上传与进度监控:XHR 仍是不可替代的方案

尽管 Fetch 支持body: FormData,但上传进度监控必须用 XHR。原因在于 Fetch 的ReadableStream无法获取上传字节数,而 XHR 的upload.onprogress提供e.loadede.total。以下是 Vue 3 中的实操封装:

<script setup> import { ref } from 'vue'; const fileInput = ref(null); const uploadProgress = ref(0); const handleUpload = async () => { const file = fileInput.value.files[0]; if (!file) return; const xhr = new XMLHttpRequest(); xhr.open('POST', '/api/upload'); xhr.upload.onprogress = (e) => { if (e.lengthComputable) { uploadProgress.value = Math.round((e.loaded / e.total) * 100); } }; xhr.onload = () => { if (xhr.status === 200) { const res = JSON.parse(xhr.responseText); console.log('上传成功:', res); uploadProgress.value = 0; } }; xhr.onerror = () => { console.error('上传失败'); uploadProgress.value = 0; }; const formData = new FormData(); formData.append('file', file); xhr.send(formData); }; </script> <template> <input type="file" ref="fileInput" @change="handleUpload" /> <div v-if="uploadProgress > 0"> 上传进度: {{ uploadProgress }}% <progress :value="uploadProgress" max="100"></progress> </div> </template>

关键点:

  • xhr.upload.onprogress是唯一能实时获取上传进度的 API;
  • e.lengthComputablefalse时(如 chunked 编码),e.total为 0,此时无法计算百分比;
  • xhr.onload在请求完成时触发,xhr.status为 HTTP 状态码,xhr.responseText为响应体。

5. 常见问题与排查技巧实录:那些文档里不会写的坑

5.1 “Failed to fetch” 的 7 种真实原因及定位方法

Failed to fetch是 Fetch 的通用错误,但背后原因千差万别。我整理了线上项目中最常遇到的 7 种情况,附带快速定位步骤:

错误现象根本原因定位方法解决方案
Failed to fetch且控制台无其他日志网络完全中断(如 WiFi 关闭)检查浏览器地址栏是否能打开其他网站;navigator.onLine返回false提示用户检查网络,禁用请求按钮
Failed to fetch伴随TypeError: Failed to fetch跨域请求被拦截(CORS)查看 Network 面板,请求状态为(blocked);Preview 为空后端配置Access-Control-Allow-Origin
Failed to fetch且请求未出现在 Network 面板AbortController.abort()被调用在代码中搜索abort();检查路由切换、组件卸载逻辑useEffect清理函数中调用abort()
Failed to fetchfetch调用后立即报错URL 协议错误(如http://调用https://API)检查fetch的 URL 字符串,确认协议一致统一使用https,或配置反向代理
Failed to fetchresponse.body为空后端返回空响应体(如 204 No Content)Network 面板查看 Response Headers,确认Content-Length: 0fetch后检查res.status !== 204再调用res.json()
Failed to fetchsignal超时AbortSignal.timeout()时间过短检查timeout参数,对比后端平均响应时间将 timeout 设为后端 P95 响应时间的 1.5 倍
Failed to fetch伴随net::ERR_CONNECTION_REFUSED后端服务未启动或端口错误ping后端域名;telnet host port测试端口连通性检查后端进程、防火墙、Docker 网络配置

实操案例:某次上线后大量用户反馈“保存失败”,错误日志全是Failed to fetch。我首先在 Network 面板过滤fetch请求,发现所有失败请求的 URL 都是http://api.xxx.com/save,而生产环境应为https。追查代码发现,环境变量VUE_APP_API_BASE_URL.env.production中误写为http://,修复后问题消失。教训:环境变量必须用console.log打印验证,不能只信文档。

5.2 Axios 拦截器的三大致命陷阱

Axios 拦截器是双刃剑,用得好提升开发效率,用不好引发线上事故。我踩过的坑:

陷阱一:拦截器中修改config导致后续请求失效

// ❌ 错误:直接修改 config.headers service.interceptors.request.use(config => { config.headers['X-Trace-ID'] = uuid(); // 每次请求覆盖 headers 对象 return config; }); // 后续请求中,config.headers 是同一个引用,可能被其他拦截器污染

✅ 正确做法:

service.interceptors.request.use(config => { const headers = { ...config.headers }; // 创建新对象 headers['X-Trace-ID'] = uuid(); return { ...config, headers }; });

陷阱二:响应拦截器中未处理undefined响应

// ❌ 错误:假设所有响应都有 data service.interceptors.response.use(res => res.data); // 当后端返回 204 No Content 时,res.data 为 undefined,导致后续 .then() 报错

✅ 正确做法:

service.interceptors.response.use( res => { if (res.status === 204) return {}; // 显式返回空对象 return res.data; } );

陷阱三:拦截器中引用组件实例造成内存泄漏

// ❌ 错误:在拦截器中使用 this.$message service.interceptors.response.use( res => res, error => { this.$message.error('请求失败'); // this 指向 Vue 实例,组件卸载后 this 仍存在 } );

✅ 正确做法:

// 在入口文件中初始化 message 实例 import { ElMessage } from 'element-plus'; const message = ElMessage; service.interceptors.response.use( res => res, error => { message.error('请求失败'); // 使用独立实例,不依赖组件 } );

5.3 Next.js 中 Fetch 的cacherevalidate配置详解

Next.js App Router 对fetch的缓存控制极其严格,配置错误会导致数据陈旧或 SSR 失败。以下是真实项目中的配置策略:

场景cachenext.revalidate说明
用户个人信息(需实时)'no-store'禁用所有缓存,每次请求都发往后端
商品列表(可容忍 30s 陈旧)'default'30默认缓存,30 秒后重新验证
静态页面(如关于我们)'force-cache'强制使用缓存,即使页面重新加载
管理后台仪表盘(需实时但允许降级)'no-cache'不缓存,但允许 CDN 缓存(需后端配合)

实操验证:在 Next.js 中,cache: 'no-store'会禁用 App Router 的所有缓存,包括 ISR(增量静态再生)。我曾为一个实时股票行情页配置cache: 'default',结果用户看到的是 1 分钟前的数据。改为cache: 'no-store'后,首屏加载变慢,但数据实时性达标。平衡点在于:对实时性要求高的页面,宁可牺牲性能也要保证正确性;对新闻列表等场景,用revalidate: 60可显著降低后端压力。

5.4 编码问题终极解决方案:ajax请求设置编码格式的真相

热搜词ajax请求设置编码格式暴露了一个长期被误解的问题。实际上:

  • GET 请求的编码由浏览器自动处理:URL 中的中文会自动 encodeURIComponent,后端用URLDecoder.decode()解码;
  • POST 请求的编码取决于Content-Type
    • application/x-www-form-urlencoded:浏览器自动 encode,后端用request.getParameter()
    • application/json:JSON 字符串本身是 UTF-8,无需额外设置;
    • multipart/form-data:文件名等字段需在FormData.append()时手动 encode。

真实案例:某政府项目中,用户上传含中文名的文件,后端收到乱码。排查发现,前端用FormData.append('file', file, '测试文件.xlsx'),而 Chrome 对文件名自动 encode 为 UTF-8,但 IE11 用 GBK。解决方案是:

// 兼容 IE11:手动 encode 文件名 const fileName = encodeURIComponent(file.name); formData.append('file', file, fileName);

后端用new String(fileName.getBytes("ISO-8859-1"), "UTF-8")解码。

最后分享一个小技巧:在 Vue 3 中,<input type="file">files[0].name在不同浏览器中编码不一致,建议统一用encodeURIComponent处理,再由后端按 UTF-8 解码。这比纠结“怎么设置 Ajax 编码”更治本。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 6:06:39

LabVIEW与CAN总线汽车电子测试上位机通用架构设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 6:06:31

电梯监控中电动车与自行车精准识别实战指南

简介&#xff1a;本资源是一套面向计算机视觉初学者与课程设计实践者的电梯监控场景目标识别项目&#xff0c;聚焦于电动车与自行车的精准检测与去重跟踪。适用于毕业设计、课程设计、工程实训及学科竞赛等教学与实践场景&#xff0c;技术栈覆盖YOLO模型微调、检测后处理与多目…

作者头像 李华
网站建设 2026/9/16 6:06:01

FH35C系列FFC/FPC连接器技术解析与应用

1. 认识FH35C系列FFC/FPC连接器在电子设备小型化与高密度集成的趋势下&#xff0c;板对板连接技术正经历着革命性的变革。作为这一领域的标杆产品&#xff0c;HIROSE广濑FH35C系列SMD连接器以其卓越的可靠性和紧凑设计&#xff0c;成为消费电子、医疗设备和工业控制领域的首选解…

作者头像 李华
网站建设 2026/9/16 6:05:25

微信支付服务商模式V3分账退款全流程解析(.NET Core实现)

简介&#xff1a;面向 .NET Core 开发者的微信支付 V3 服务商模式源码包&#xff0c;覆盖普通支付、服务商模式支付、分账给个人、退款、支付回写等业务&#xff0c;适合平台型电商、多商户系统或需要接入微信支付分账能力的项目&#xff0c;阅读者需具备 C# 基础。资源共 696 …

作者头像 李华
网站建设 2026/9/16 6:04:11

微信小程序球馆预约系统:SSM后端与并发防超卖实战解析

简介&#xff1a;微信小程序球馆预约系统SSM后端源码案例设计&#xff0c;是一套适合毕业设计、期末大作业与Spring/SpringMVC/MyBatis入门练习的完整项目案例。项目以后端开发为主线&#xff0c;涵盖Spring依赖注入与事务管理、SpringMVC请求调度、MyBatis持久层映射、小程序W…

作者头像 李华
网站建设 2026/9/16 6:03:50

Fast DDS发现与传输机制深度解析:从QoS配置到工业实时通信落地

1. Fast DDS 到底怎么用&#xff1f;先搞清它不是“另一个ROS通信层”Fast DDS&#xff08;原eProsima Fast RTPS&#xff09;不是个“开箱即用”的聊天工具&#xff0c;也不是像HTTP那样你发个GET就能拿到数据的协议。它是一套严格遵循DDS&#xff08;Data Distribution Servi…

作者头像 李华