ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

axios 傻瓜式入门:GET/POST 请求与请求头参数详解

axios 傻瓜式入门:GET/POST 请求与请求头参数详解

前端开发里,跟后端打交道最频繁的动作就是发请求。早些年大家用原生XMLHttpRequest写一长串回调,后来 jQuery 的$.ajax火了一阵,再往后项目工程化程度越来越高,axios就成了绝大多数人绕不开的一个 HTTP 客户端库。这个标题里带“傻瓜式入门”,我理解得很直白:不绕弯子,把 GET、POST 怎么发、请求头参数怎么加这几件事讲透,让刚上手的人照着就能跑起来。我自己带过不少刚入行的同学,发现大家卡住的地方往往不是“不会写 axios.get”,而是不知道请求头该往哪塞、POST 的 body 和 params 到底啥区别、拦截器配了为什么没生效。这篇就把这些坑一个个填平,从最基础的调用讲到头部参数的几种添加方式,再补上实际项目里踩过的经验。不管你是刚学前端、正在做第一个接口联调项目,还是已经写过几行但总觉得没吃透,都能从里面找到能直接抄的东西。

1. 先搞清楚 axios 到底是个什么东西

1.1 它解决的核心问题:把请求这件事标准化

很多人对 axios 的第一印象是“一个发请求的库”,这话没错但太浅。它真正值钱的地方在于把浏览器和 Node 环境下的 HTTP 请求抽象成了一套统一的 Promise 接口。原生fetch虽然现代浏览器都支持,但它有几个让人头疼的点:默认不带 cookie、4xx/5xx 状态码不会 reject、没有请求超时配置、JSON 解析还得自己.json()一下。axios 把这些都包好了,你拿到response.data就是解析好的对象,状态码非 2xx 会直接进 catch,超时、取消、拦截器这些能力开箱即用。

从工程角度看,axios 的另一个价值是可配置的层级结构。它支持全局默认配置、实例级配置、单次请求配置三层,优先级从低到高。这意味着你可以在项目入口统一设baseURL和超时时间,在某个业务模块的实例上单独加认证头,再在个别请求里覆盖特殊参数。这种分层设计是它比裸写fetch更适合中大型项目的原因。

提示:axios 在浏览器端底层用的是XMLHttpRequest,在 Node 端用的是http/https模块。所以它不存在“浏览器专属”的问题,同一套代码在服务端渲染场景里也能跑,这是它相比很多浏览器专用请求库的优势。

1.2 GET 和 POST 的本质区别,别只记“一个查一个改”

新手最容易混淆的就是 GET 和 POST 的参数放哪。我用一句话概括:GET 的参数拼在 URL 上,POST 的参数放在请求体里。但这句话背后有更实际的含义。GET 请求的参数会出现在地址栏、浏览器历史、服务器访问日志里,所以它天然不适合传敏感信息,也有长度限制(虽然现代浏览器放宽了很多,但服务端和代理层仍可能截断)。POST 把数据放在 body 里,不受 URL 长度约束,适合传大段 JSON、文件、表单。

在 axios 里,这个区别体现得很具体:GET 用params配置项,axios 会自动帮你做 URL 编码并拼接到问号后面;POST 用data配置项,axios 会根据Content-Type决定怎么序列化。搞混这两个配置项是新手最常见的错误之一,后面我会专门用一节讲清楚。

1.3 为什么入门阶段建议先用 CDN 引入

刚学的时候不建议一上来就搞 npm 安装加打包工具,那会把注意力从“请求怎么写”转移到“环境怎么配”。直接用<script>标签引入 CDN 上的 axios,打开一个 HTML 文件就能在控制台里试,反馈最快。等你把 GET、POST、请求头这些玩熟了,再迁到工程化项目里,那时候配baseURL、拦截器才有意义。这个顺序很重要,先会用再会配,别本末倒置。

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

引入之后全局会挂一个axios对象,直接在<script>里或者浏览器控制台调用即可。下面所有的例子都基于这个前提,你复制过去就能跑。

2. GET 请求:从最简单的调用到参数拼接

2.1 最简形式与返回值结构

一个最朴素的 GET 请求长这样:

axios.get('https://api.example.com/users') .then(response => { console.log(response.data); }) .catch(error => { console.error(error); });

这里要重点理解返回的response对象结构,它不是一个裸数据,而是包了一层。常用的几个字段:data是服务端返回的响应体(axios 自动做了 JSON 解析)、status是 HTTP 状态码、headers是响应头、config是这次请求的完整配置。新手经常直接console.log(response)然后疑惑“我要的数据怎么埋在里面”,答案就是取response.data。

用async/await写法会更清爽,这也是现在项目里的主流写法:

async function fetchUsers() { try { const response = await axios.get('https://api.example.com/users'); return response.data; } catch (error) { console.error('请求失败:', error.message); } }

2.2 用 params 传查询参数,别手动拼字符串

带查询参数的 GET 请求,正确做法是用params配置项:

axios.get('https://api.example.com/users', { params: { page: 1, pageSize: 20, keyword: '张三' } });

axios 会自动把它拼成?page=1&pageSize=20&keyword=%E5%BC%A0%E4%B8%89,中文和特殊字符的 URL 编码它帮你处理了。如果你手动拼字符串,很容易漏掉encodeURIComponent,遇到中文、空格、&符号就出问题。我见过有同学写url + '?name=' + name,结果名字里带个&直接把参数截断了,排查半天。

params还有个好处是值为null或undefined的键会被自动忽略,不会拼出?keyword=undefined这种脏 URL。这个细节在表单筛选场景里特别实用,用户没填的字段直接传undefined就行。

2.3 路径参数和查询参数别搞混

RESTful 风格的接口经常有路径参数,比如/users/123里的123。这种不是查询参数,得用模板字符串拼进 URL:

const userId = 123; axios.get(`https://api.example.com/users/${userId}`);

判断标准很简单:看接口文档里参数是写在路径段里还是问号后面。路径段用模板字符串,问号后面用params。两者可以同时存在:

axios.get(`https://api.example.com/users/${userId}/orders`, { params: { status: 'paid', page: 1 } });

3. POST 请求:body 怎么传、格式怎么对

3.1 data 配置项与默认序列化行为

POST 请求的数据放在data里:

axios.post('https://api.example.com/users', { name: '李四', age: 25 });

这里有个关键点必须讲清楚:axios 默认会把普通对象序列化成 JSON,并自动设置Content-Type: application/json。也就是说上面这段代码发出去的请求头里已经带好了 JSON 类型,body 是{"name":"李四","age":25}。很多人不知道这个默认行为,又手动加了一遍Content-Type,虽然不报错但属于多余操作。

如果你的后端要的是表单格式(application/x-www-form-urlencoded),那就不能直接传对象,得用URLSearchParams或者qs库转换:

const params = new URLSearchParams(); params.append('name', '李四'); params.append('age', 25); axios.post('https://api.example.com/users', params);

用URLSearchParams时 axios 会自动把Content-Type设成application/x-www-form-urlencoded,body 变成name=%E6%9D%8E%E5%9B%9B&age=25。这个转换过程新手经常漏掉,结果后端收到一个 JSON 字符串却按表单解析,拿不到字段。

3.2 POST 也能带查询参数

这点很多人不知道:POST 请求同样可以用params配置项往 URL 上挂查询参数,和 body 里的data互不干扰。

axios.post('https://api.example.com/users', { name: '李四' }, // 请求体 { params: { source: 'web' } } // URL 查询参数 );

注意axios.post的第二个参数是 body,第三个参数才是配置对象。这个签名顺序是新手高频出错点,把params写到第二个参数位置,结果整个对象被当成 body 发出去了。记住口诀:get 的第二个参数是配置,post 的第二个参数是数据、第三个才是配置。

3.3 三种传参方式对照表

为了把这块彻底理清,我整理了一张对照表,实际开发中照着查就行:

场景配置项出现位置示例
GET 查询参数paramsURL 问号后?page=1
POST 查询参数params(第三个参数)URL 问号后?source=web
POST 请求体data(第二个参数)请求 body{"name":"李四"}
路径参数模板字符串URL 路径段/users/123

4. 请求头参数:几种添加方式与优先级

4.1 单次请求里加 headers

最直接的方式是在配置对象里写headers:

axios.get('https://api.example.com/profile', { headers: { 'Authorization': 'Bearer your-token-here', 'X-Request-Id': 'abc-123' } });

POST 请求同理,放在第三个参数里:

axios.post('https://api.example.com/users', { name: '李四' }, { headers: { 'Authorization': 'Bearer your-token-here' } } );

这种方式适合个别请求需要特殊头的场景,比如某个接口要额外的签名头。缺点是每个请求都写一遍很啰嗦,token 变了还得全局改。

4.2 全局默认头与实例级头

项目里最常见的做法是设全局默认头:

axios.defaults.headers.common['Authorization'] = 'Bearer your-token-here'; axios.defaults.headers.post['Content-Type'] = 'application/json';

headers.common对所有请求生效,headers.get、headers.post分别只对对应方法生效。登录成功后把 token 塞进common,退出登录时删掉,这是很经典的做法。

但全局配置有个隐患:一个页面里如果有多个不同后端服务,全局头会污染所有请求。这时候用实例更合适:

const userApi = axios.create({ baseURL: 'https://api.example.com/user', timeout: 5000, headers: { 'Authorization': 'Bearer user-token' } }); const adminApi = axios.create({ baseURL: 'https://api.example.com/admin', timeout: 10000, headers: { 'Authorization': 'Bearer admin-token' } });

两个实例各自带头,互不干扰。这是我个人最推荐的组织方式,尤其是中大型项目,按业务域拆实例比全局配置清晰得多。

4.3 拦截器里统一加头,动态 token 的正确姿势

token 存在本地、会过期、会刷新,写死在配置里不现实。标准做法是用请求拦截器:

axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); });

每次请求发出前都会读一次最新的 token,登录、刷新 token 后无需改任何调用代码。这里有个细节要注意:拦截器里改的是config.headers,不是重新赋值整个 headers 对象,直接config.headers = {...}在某些版本里会丢掉 axios 内部的默认头。

4.4 头部优先级:谁覆盖谁

三层配置的优先级从高到低是:单次请求 headers > 实例 headers > 全局 defaults。也就是说单次请求里写的同名头会覆盖实例和全局的。理解这个优先级,排查“为什么我设的头没生效”时就有方向了——先看是不是被更高优先级的配置覆盖了。

注意:Content-Type这个头比较特殊,axios 会根据data的类型自动推断。如果你手动设了Content-Type: application/json但传的是URLSearchParams,就会出现头和 body 不匹配的情况,后端解析失败。这种问题不会报错,只会表现为“后端收不到参数”,排查起来很费时间。

5. 完整实操:一个可运行的请求封装示例

5.1 从零搭一个带认证的请求模块

把前面讲的东西串起来,写一个实际项目里能用的封装。假设我们要做一个带 token 认证、统一错误处理、超时控制的请求模块:

const request = axios.create({ baseURL: 'https://api.example.com', timeout: 8000, headers: { 'Content-Type': 'application/json' } }); request.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, error => Promise.reject(error)); request.interceptors.response.use( response => response.data, error => { if (error.response) { const status = error.response.status; if (status === 401) { console.warn('登录已过期,请重新登录'); } else if (status >= 500) { console.error('服务端异常'); } } else if (error.code === 'ECONNABORTED') { console.error('请求超时'); } return Promise.reject(error); } ); export default request;

这个封装做了几件事:统一baseURL省得每次写全路径、8 秒超时防止请求挂死、请求拦截器自动带 token、响应拦截器直接把response.data剥出来(调用方不用再.data)、统一处理 401 和 5xx。这套结构我在多个项目里用过,改动成本低,扩展也方便。

5.2 调用侧怎么写才干净

封装好之后,业务代码就非常薄了:

async function loadUserList(page) { const data = await request.get('/users', { params: { page } }); return data.list; } async function createUser(payload) { return await request.post('/users', payload); }

因为响应拦截器已经剥了data,这里拿到的直接就是业务数据。注意createUser里没有传 headers,token 由拦截器统一加,这就是分层配置带来的好处——业务代码只关心业务参数。

5.3 参数计算的一个实际例子:分页偏移量

有时候后端要的是偏移量而不是页码,需要前端算一下。假设每页 20 条,第page页(从 1 开始)的偏移量是(page - 1) * 20:

const pageSize = 20; const page = 3; const offset = (page - 1) * pageSize; // 40 request.get('/users', { params: { offset, limit: pageSize } });

这种计算看着简单,但边界要留意:page从 0 开始还是从 1 开始,前后端约定必须一致,否则第一页会漏数据或多数据。我踩过一次,后端页码从 0 开始,前端从 1 开始传,结果第一页永远是空的,查了半天才发现是约定问题。

6. 常见问题与排查速查

6.1 高频报错与对应原因

实际开发中遇到的 axios 问题,八成集中在下面这几类,我整理成速查表:

现象可能原因排查方向
后端收不到 POST 参数Content-Type 与 body 格式不匹配检查是否传了对象却声明表单类型
请求头里没有 Authorization拦截器未生效或 token 为空打印 config.headers 确认
跨域报错 CORS服务端未放行来源这是服务端配置问题,前端改不了
请求超时timeout 设太短或网络慢调大 timeout 或检查接口耗时
拿到的是字符串不是对象响应不是 JSON 或未设响应类型检查responseType和响应头
params 里的中文乱码手动拼 URL 未编码改用 params 配置项

6.2 几个容易忽略的细节

跨域问题不是 axios 的锅。很多人第一次遇到 CORS 报错就怀疑 axios 配置,其实这是浏览器的同源策略限制,解决只能靠服务端加响应头,或者开发阶段用构建工具的代理转发。axios 层面无论怎么配都绕不过去,这点要认清,别在错误的方向上折腾。

params和data同时存在时别慌。有些接口既要在 URL 上带筛选条件,又要在 body 里传数据,两个配置项一起用完全没问题,它们走的是不同通道。

取消重复请求。搜索框输入时如果每次按键都发请求,很容易出现后发先至的乱序问题。可以用AbortController取消上一次请求:

let controller = null; async function search(keyword) { if (controller) controller.abort(); controller = new AbortController(); try { const data = await request.get('/search', { params: { keyword }, signal: controller.signal }); return data; } catch (e) { if (axios.isCancel(e)) return; // 主动取消的不算错误 throw e; } }

这段代码在搜索联想场景里非常实用,能避免旧请求覆盖新结果。

6.3 我踩过的几个坑

第一个坑是在拦截器里返回了response却在业务里又取.data。响应拦截器如果写了response => response.data,业务侧就直接拿数据;如果写的是response => response,业务侧才需要.data。两种风格都行,但一个项目里必须统一,混用必然出 bug。

第二个坑是全局设置了Content-Type导致文件上传失败。上传文件要用multipart/form-data,如果全局默认头写死了application/json,文件请求也会带上错误的类型。正确做法是上传时单独覆盖,或者干脆不设全局Content-Type,让 axios 按数据类型自动推断。

第三个坑是token 过期后没有统一跳转。401 处理如果散落在各个业务代码里,维护起来很痛苦。放在响应拦截器里统一处理,配合一个标志位防止多个请求同时触发多次跳转,这个细节在实际项目里很关键。

7. 从入门到顺手:几条实用建议

学 axios 这件事,我的体会是先跑通再优化。一开始别急着上拦截器、封装、取消请求这些进阶内容,先用最简单的axios.get和axios.post把接口调通,看到数据回来,建立信心。等基础调用熟了,再逐步引入实例、拦截器、统一错误处理,每一步都对应一个真实痛点,这样学起来不空洞。

关于请求头的添加,我给个选择建议:单个请求的特殊头写在配置里,业务模块共用的头用实例,全局认证头用拦截器动态注入。这三层各司其职,别把所有头都堆在全局,也别每个请求都手写一遍。判断标准就是“这个头的作用范围有多大”,范围决定层级。

最后分享一个调试小技巧:排查请求问题时,打开浏览器开发者工具的 Network 面板,点开具体请求看 Request Headers 和 Payload 两个区域,头对不对、body 格式对不对一目了然。比在代码里到处console.log高效得多。很多时候问题不在 axios 配置,而在于你以为发出去的东西和实际发出去的东西不一致,Network 面板就是照妖镜。

返回列表