一、Axios 是啥?为啥 Vue2 项目里选它做请求?
不少同学刚接触 Vue2 做项目时,一碰到网络请求就犯懵 —— Axios 到底咋用?不同请求咋发?拦截器、封装这些概念咋落地?跨域报错咋解决?这篇把 Vue2 + Axios 开发里的常见问题拆碎了讲,从基础到实战一步步说清楚,新手也能跟着理顺思路~
Axios 是个基于 Promise 的 HTTP 客户端,浏览器和 Node.js 环境都能跑,Vue2 生态里常用它做请求,核心原因有这些: - Vue 本身不内置请求库,得自己选工具; - Axios 语法友好,用 `axios.get()` `axios.post()` 这类链式调用,比原生 `fetch` 或 `XMLHttpRequest` 好懂; - 支持**拦截器**(请求发出去前、响应回来后统一处理)、**取消请求**(避免重复请求)、**自动转换 JSON** 这些实用特性; - 社区资源多,碰到问题一搜一大把解决方案,学习成本低。Vue2 项目里咋引入 Axios?全局和局部用法有啥区别?
先把 Axios 装到项目里:用 npm install axios
或者 yarn add axios
都行,接下来分全局引入和局部引入两种方式:
全局引入(推荐高频请求场景)
在项目的 main.js
里这么写:
import Vue from 'vue' import axios from 'axios' // 把 axios 挂到 Vue 原型上,所有组件都能通过 this.$axios 调用 Vue.prototype.$axios = axios
这样做的好处是不用每个组件都重复 import,写请求时直接 this.$axios.get(...)
就行,但如果项目里有多个不同域名的接口(比如一个调内部系统,一个调第三方),全局配置可能不够灵活,这时候得结合「实例化封装」(后面会讲)。
局部引入(适合低频/特殊配置请求)
如果只是某个组件要用 Axios,就在组件里单独引入:
import axios from 'axios' export default { methods: { fetchData() { axios.get('/api/data').then(...) } } }
局部引入的优势是配置更灵活,比如给这个请求单独设超时时间、请求头,但缺点是代码重复率高,多个组件用的话得重复写 import
。
最基础的 GET/POST 请求咋写?参数咋传?
实际开发里,GET 常用在「查数据」(比如列表查询),POST 常用在「提交数据」(比如登录、上传),这两种请求的参数传递方式不一样,得注意细节:
GET 请求:参数走 params
(自动拼到 URL 后)
比如做一个「文章列表查询」,需要传页码和每页数量:
this.$axios.get('/api/articles', { params: { page: 1, size: 10 } }).then(res => { console.log(res.data) // 后端返回的数据 }).catch(err => { console.error('请求失败:', err) })
Axios 会自动把 params
里的对象转成 ?page=1&size=10
拼到 URL 后面,还会帮你处理特殊字符的编码,不用自己操心。
POST 请求:参数走 data
(默认发 JSON)
用户登录」需要传账号密码,用 POST 发 JSON 数据:
this.$axios.post('/api/login', { username: 'test', password: '123456' }).then(res => { // 登录成功逻辑,比如存 token }).catch(err => { // 错误处理,比如提示“账号密码错误” })
如果后端需要「表单格式(form-data)」(比如上传文件),得用 FormData
构造数据,还要手动设请求头:
// 假设 fileObj 是文件对象(<input type="file"> 选的文件) let formData = new FormData() formData.append('file', fileObj) this.$axios.post('/api/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } })
这里要注意:Axios 默认 POST 请求头是 application/json
,如果要发 form-data,必须手动把请求头改成 multipart/form-data
,否则后端可能解析不了。
拦截器有啥用?请求和响应拦截器咋配置?
拦截器能在「请求发出去前」和「响应回来后」做统一处理,减少重复代码,比如给所有请求加 token、全局处理错误码,都靠它:
请求拦截器:给所有请求加 token
很多接口需要验证身份,得在请求头里带 token,用请求拦截器,不用每个请求单独写:
// 先引入 axios(如果是全局配置,直接用 Vue.prototype.$axios) import axios from 'axios' axios.interceptors.request.use( config => { // 从 localStorage 里拿 token(假设登录后存在这里) const token = localStorage.getItem('token') if (token) { // 把 token 加到请求头,格式看后端要求,这里是 Bearer 格式举例 config.headers.Authorization = `Bearer ${token}` } return config // 必须 return 配置,请求才会发出去 }, error => { // 请求还没发就出错了(比如参数错了),直接 reject return Promise.reject(error) } )
这样配置后,所有通过这个 axios 实例发的请求,请求头都会自动带上 token,不用每个请求手动写。
响应拦截器:全局处理错误码
后端接口一般会返回统一的结构({ code, data, msg }
),用响应拦截器可以提前处理错误:
axios.interceptors.response.use( response => { // 假设后端约定 code=200 是成功,其他是业务错误 if (response.data.code === 401) { // 401 代表未登录,跳转到登录页(用 Vue Router 的话,this.$router.push) window.location.href = '/login' } else if (response.data.code !== 200) { // 业务错误,密码错误”“权限不足”,全局提示 alert(response.data.msg) } // 把响应里的 data 直接返回,组件里不用再写 res.data return response.data }, error => { // 处理网络错误(比如超时、断网) if (error.message.includes('timeout')) { alert('请求超时,请重试') } else if (!window.navigator.onLine) { alert('网络连接异常') } return Promise.reject(error) } )
配置后,组件里调接口时,then
里直接拿到的是 response.data
,不用再判断 code
,代码更简洁。
为啥要封装 Axios?怎么封装更实用?
直接用全局 axios 有个问题:不同环境(开发、测试、生产)的基础 URL 不好切换,而且拦截器改动得改全局配置,维护起来麻烦。「封装 Axios」就是创建自定义实例,让配置更灵活:
步骤:创建实例 + 加拦截器 + 导出
在项目里新建 utils/request.js
(路径自己定),写这样的代码:
import axios from 'axios' // 创建 axios 实例,自定义配置 const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // 不同环境配 .env 文件(比如开发环境是 /api,生产是 https://xxx.com) timeout: 5000, // 全局超时时间,5 秒没响应就算超时 headers: { 'Content-Type': 'application/json' } // 全局请求头 }) // 给实例加请求拦截器(和之前全局配置逻辑一样,只是作用于这个实例) service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => { return Promise.reject(error) } ) // 给实例加响应拦截器 service.interceptors.response.use( response => { if (response.data.code === 401) { window.location.href = '/login' } else if (response.data.code !== 200) { alert(response.data.msg) } return response.data }, error => { if (error.message.includes('timeout')) { alert('请求超时') } return Promise.reject(error) } ) // 导出实例,其他文件用这个实例发请求 export default service
组件里咋用封装后的实例?
在需要发请求的组件里,引入这个实例:
import request from '@/utils/request' // 路径对应你放 request.js 的位置 export default { methods: { fetchArticle() { request.get('/articles', { params: { page: 1 } }).then(res => { // res 已经是 response.data 了,直接用 }).catch(err => { // 错误处理 }) } } }
封装的好处很明显:不同模块可以创建多个实例(比如一个调内部接口,一个调第三方接口),每个实例的基础 URL、超时时间、拦截器都是独立的,维护起来更灵活。
Vue2 里 Axios 跨域问题咋解决?开发和生产有啥区别?
跨域是浏览器的「同源策略」导致的(协议、域名、端口有一个不一样,就会跨域),开发和生产环境的解决思路不同:
开发环境(Vue CLI 项目):用代理转发
在项目根目录的 vue.config.js
里配置代理:
module.exports = { devServer: { proxy: { '/api': { // 匹配所有以 /api 开头的请求 target: 'https://xxx.com', // 后端真实域名 changeOrigin: true, // 开启代理:虚拟一个和后端同域的请求源,绕开浏览器拦截 pathRewrite: { '^/api': '' } // 把 /api 替换成空,比如请求 /api/login 实际发到 https://xxx.com/login } } } }
配置后,前端发请求到 /api/login
,会被代理到 https://xxx.com/login
,浏览器里看请求是前端自己的域名(http://localhost:8080
),就不会触发跨域报错。
生产环境:用 Nginx 反向代理
开发时的 devServer.proxy
只在本地生效,生产环境得用 Nginx 配置反向代理,在 Nginx 的配置文件(nginx.conf
)里加:
server { listen 80; # 前端项目运行的端口 server_name 你的域名; # www.xxx.com location /api { # 匹配 /api 开头的请求 proxy_pass https://xxx.com; # 后端真实地址 proxy_set_header Host $host; # 把请求头里的 Host 设为前端域名,让后端识别 proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实 IP } }
这样生产环境下,前端请求 /api/login
会被 Nginx 转发到后端,同样绕开跨域问题。
Axios 错误处理咋做更完善?
除了拦截器里的全局处理,组件里调接口时也要「分层处理」,区分网络错误和业务错误:
组件内的错误处理逻辑
假设用封装后的 request
实例发请求,代码可以这么写:
request.post('/login', { username, password }) .then(res => { // 拦截器已经把 response.data 返回来,这里 res 就是后端数据 if (res.code === 200) { // 登录成功:存 token、跳首页等 localStorage.setItem('token', res.data.token) this.$router.push('/home') } else { // 业务错误:账号不存在”“密码错误”,给用户提示 alert(res.msg) } }) .catch(err => { // 网络错误:比如拦截器里没处理完的超时、断网 console.log('请求失败原因:', err) alert('网络波动,请稍后再试') })
进阶:取消重复请求
如果用户快速点按钮,同一个请求发了多次,会给后端增加压力,用 Axios 的 CancelToken
可以取消重复请求:
// 在 request.js 里加这段逻辑(给封装的实例用) const pending = new Map() // 存请求标识和取消函数 // 生成请求唯一标识(method + url + params + data) const getRequestKey = (config) => { return [config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data)].join('&') } // 请求拦截器里加“取消重复请求”逻辑 service.interceptors.request.use(config => { const key = getRequestKey(config) // 如果之前有相同的请求在pending,取消它 if (pending.has(key)) { pending.get(key)() // 执行取消操作 pending.delete(key) // 从map里删掉 } // 给当前请求加 CancelToken config.cancelToken = new axios.CancelToken(cancel => { pending.set(key, cancel) }) return config }, error => { return Promise.reject(error) }) // 响应拦截器里移除已完成的请求 service.interceptors.response.use(response => { const key = getRequestKey(response.config) pending.delete(key) // 请求完成,从map里删掉 return response }, error => { // 如果是主动取消的请求,不视为错误 if (axios.isCancel(error)) { return new Promise(() => {}) // 空Promise,组件里catch不会触发 } return Promise.reject(error) })
这样配置后,同一时间重复的请求会被自动取消,减少后端压力。
Axios 和 Vuex 结合咋玩?异步请求咋管理状态?
Vuex 里的 action
适合处理异步操作(比如登录、获取用户信息),可以把请求逻辑放到 action
里,用 mutation
更新状态:
例子:用户登录 + 获取信息流程
在 store/modules/user.js
里写:
import request from '@/utils/request' // 引入封装好的 axios 实例 const state = { token: '', userInfo: {} } const mutations = { // 存 token SET_TOKEN(state, token) { state.token = token }, // 存用户信息 SET_USER_INFO(state, info) { state.userInfo = info } } const actions = { // 登录 action,接收用户名和密码 login({ commit }, { username, password }) { return new Promise((resolve, reject) => { request.post('/login', { username, password }) .then(res => { if (res.code === 200) { // 登录成功,存 token commit('SET_TOKEN', res.data.token) // 接着获取用户信息 return request.get('/user/info') } return res // code 不对,把结果抛出去 }) .then(userRes => { // 存用户信息 commit('SET_USER_INFO', userRes.data) resolve(userRes) // 通知组件登录成功 }) .catch(err => { reject(err) // 通知组件登录失败 }) }) } } export default { namespaced: true, state, mutations, actions }
组件里触发这个 action
:
export default { methods: { handleLogin() { this.$store.dispatch('user/login', { username: this.username, password: this.password }) .then(() => { // 登录成功,跳转到首页 this.$router.push('/home') }) .catch(err => { // 登录失败,提示错误 alert('登录失败:' + err.message) }) } } }
把请求逻辑放到 Vuex 的 action
里,状态更新用 mutation
,组件只负责「触发 action」和「处理结果」,代码分层更清晰,维护起来也方便。
实际开发里还有哪些 Axios 小技巧?
除了基础用法,这些细节能帮你避坑:
参数序列化:复杂对象转 URL 参数
GET
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。