Vue 请求后端数据与跨域问题
这篇文章迁移自我早年写在博客园的一篇记录。当时遇到的问题很直接:后端接口已经写好了,Vue 前端应该怎么请求数据?请求时报跨域又应该怎么处理?
现在回头看,这其实是前后端分离开发里非常典型的一步:前端项目跑在一个端口,后端服务跑在另一个端口,浏览器因为同源策略限制,默认不允许它们随便互相请求。
这篇文章按现在的写法重新整理一遍:先用 axios 发起请求,再抽出请求模块,最后处理跨域问题。
假设后端提供了一个用户列表接口:
GET http://localhost:8088/user前端 Vue 项目运行在另一个地址,例如:
http://localhost:8080/这两个地址的协议、域名或端口只要有一个不同,就不是同源。这里端口不同,所以浏览器会把它们视为跨域请求。
安装 axios
Section titled “安装 axios”前端请求接口可以使用 axios。
npm install axios安装后,在需要请求数据的 Vue 页面中引入:
import axios from 'axios';如果项目使用的是 Vue 2 或 Options API,可以先在 methods 中写一个简单请求方法。
export default { methods: { fetchUsers() { axios // 这里填写后端接口地址 .get('http://localhost:8088/user') .then(function (response) { // 请求成功后,后端返回的数据在 response.data 中 console.log(response.data); }) .catch(function (error) { // 请求失败时会进入 catch console.log(error); }) .finally(function () { // 不管成功还是失败,finally 都会执行 console.log('请求结束'); }); }, },};页面上可以绑定一个按钮测试请求:
<button @click="fetchUsers">请求用户数据</button>点击按钮后,打开浏览器控制台。如果后端接口正常,并且跨域已经允许,就能在控制台看到返回数据。
async 写法
Section titled “async 写法”如果你更喜欢同步风格,也可以用 async / await。
import axios from 'axios';
export default { methods: { async fetchUsers() { try { // await 会等待请求完成 const response = await axios.get('http://localhost:8088/user');
// 后端真正返回的数据通常放在 response.data console.log(response.data); } catch (error) { // 网络错误、接口错误、跨域错误都会进入这里 console.log(error); } }, },};这种写法在业务代码里更容易读,尤其是请求成功后还有多步处理时。
为什么要封装请求
Section titled “为什么要封装请求”刚开始写一个请求时,直接在页面里写完整地址没有问题。
但接口一多,就会出现几个问题:
- 每个页面都要写
http://localhost:8088。 - 请求地址散落在各个组件里。
- 后续换服务器地址时,需要到处改。
- 请求头、错误处理、登录 token 等逻辑难以统一。
所以更常见的做法是:把 axios 实例单独封装起来,再把具体接口拆到 api 目录里。
推荐目录结构
Section titled “推荐目录结构”可以按下面的方式组织前端请求代码:
文件夹src/
文件夹api/
- user.js
文件夹utils/
- request.js
文件夹views/
- UserList.vue
这里的职责可以这样理解:
utils/request.js:创建 axios 实例,统一配置基础地址、超时时间、拦截器。api/user.js:按业务模块封装用户相关接口。views/UserList.vue:页面组件,只关心调用哪个接口,不关心底层请求细节。
封装 axios 实例
Section titled “封装 axios 实例”在 src/utils/request.js 中写入:
import axios from 'axios';
const request = axios.create({ // 接口服务器地址 // 后续接口只需要写 /user、/login 这类路径 baseURL: 'http://localhost:8088',
// 超时时间,单位是毫秒 timeout: 10000,});
// 请求拦截器:请求发出前会先经过这里request.interceptors.request.use( function (config) { // 如果后续有 token,可以在这里统一添加 // config.headers.Authorization = `Bearer ${token}`; return config; }, function (error) { return Promise.reject(error); });
// 响应拦截器:后端响应后会先经过这里request.interceptors.response.use( function (response) { // 这里先直接返回 response // 如果后端有统一结构,也可以只返回 response.data return response; }, function (error) { // 可以在这里统一处理错误提示、登录过期等问题 return Promise.reject(error); });
export default request;这样封装后,页面里就不需要每次都写完整后端地址了。
封装接口方法
Section titled “封装接口方法”在 src/api/user.js 中写:
import request from '../utils/request';
export function getUserList(params = {}) { return request({ // axios 里字段名是 method,不是 methods method: 'GET',
// 会和 baseURL 拼接成 http://localhost:8088/user url: '/user',
// GET 请求参数 params, });}这里要注意一个细节:axios 配置里的请求方式字段是 method,不是 methods。
在 Vue 页面中调用
Section titled “在 Vue 页面中调用”在页面组件中引入接口方法:
import { getUserList } from '../api/user';
export default { data() { return { tableData: [], }; },
methods: { async loadUsers() { try { const response = await getUserList();
// 如果响应拦截器返回的是完整 response,就从 response.data 取数据 this.tableData = response.data; } catch (error) { console.log(error); } }, },};如果希望页面创建时自动请求,可以在 created 生命周期中调用:
import { getUserList } from '../api/user';
export default { data() { return { tableData: [], }; },
created() { // 页面创建时加载数据 this.loadUsers(); },
methods: { async loadUsers() { const response = await getUserList(); this.tableData = response.data; }, },};如果要在模板中展示:
<template> <ul> <li v-for="user in tableData" :key="user.id"> {{ user.name }} </li> </ul></template>实际字段名要根据后端返回数据决定。
跨域问题是什么
Section titled “跨域问题是什么”前端请求后端时,如果浏览器控制台出现类似错误:
Access to XMLHttpRequest at 'http://localhost:8088/user'from origin 'http://localhost:8080'has been blocked by CORS policy这就是跨域问题。
它不是 axios 的问题,也不是 Vue 的问题,而是浏览器的同源策略在生效。
同源要求三者完全一致:
- 协议相同,例如都是
http。 - 域名相同,例如都是
localhost。 - 端口相同,例如都是
8080。
下面两个地址端口不同,所以不是同源:
前端:http://localhost:8080后端:http://localhost:8088浏览器会拦截前端 JavaScript 对后端的请求,除非后端明确允许这个来源访问。
后端使用 CrossOrigin
Section titled “后端使用 CrossOrigin”如果后端是 Spring Boot,可以在 Controller 上添加 @CrossOrigin。
import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
@RestController@CrossOriginpublic class UserController {
@GetMapping("/user") public List<User> listUsers() { return userService.listUsers(); }}这样写表示这个 Controller 允许跨域访问。
如果想限制来源,可以写得更明确:
@CrossOrigin(origins = "http://localhost:8080")@RestControllerpublic class UserController { // ...}这比完全放开更安全。
全局跨域配置
Section titled “全局跨域配置”如果接口很多,不想每个 Controller 都写 @CrossOrigin,也可以做全局配置。
import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configurationpublic class WebConfig implements WebMvcConfigurer {
@Override public void addCorsMappings(CorsRegistry registry) { registry // 哪些接口路径允许跨域 .addMapping("/**")
// 允许哪个前端地址访问 .allowedOrigins("http://localhost:8080")
// 允许哪些请求方法 .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
// 允许携带哪些请求头 .allowedHeaders("*"); }}开发阶段可以先这样配置。正式环境中,allowedOrigins 不建议随便写成 *,最好明确填写真实前端域名。
前端代理方案
Section titled “前端代理方案”开发环境也可以通过前端代理解决跨域。
如果使用 Vue CLI,可以在 vue.config.js 中配置:
module.exports = { devServer: { proxy: { '/api': { // 后端服务地址 target: 'http://localhost:8088',
// 是否改变请求来源 changeOrigin: true,
// 把 /api/user 重写成 /user pathRewrite: { '^/api': '', }, }, }, },};这时 axios 的基础地址可以改成:
const request = axios.create({ baseURL: '/api',});请求:
request({ method: 'GET', url: '/user',});浏览器实际请求的是前端开发服务器:
http://localhost:8080/api/user再由前端开发服务器转发给后端:
http://localhost:8088/user这样浏览器看到的是同源请求,就不会触发跨域限制。
后端能访问,前端却报跨域
Section titled “后端能访问,前端却报跨域”浏览器跨域是浏览器限制。你用 Postman、Apifox、curl 能请求成功,不代表浏览器里也能请求成功。
axios 报错是不是后端没返回数据
Section titled “axios 报错是不是后端没返回数据”不一定。跨域错误时,请求可能已经被浏览器拦截,前端拿不到正常响应。要先看浏览器控制台的 Network 和 Console。
请求写对了,但 this 取不到
Section titled “请求写对了,但 this 取不到”如果使用普通函数,this 可能发生变化。
getUserList().then(function (response) { // 这里的 this 不一定是 Vue 组件实例 this.tableData = response.data;});可以改成箭头函数:
getUserList().then((response) => { // 箭头函数不会重新绑定 this this.tableData = response.data;});或者直接使用 async / await。
Vue 请求后端接口时,可以按这个顺序处理:
- 使用 axios 先完成一次最简单的请求。
- 确认后端接口能正常返回数据。
- 如果浏览器报跨域,让后端配置 CORS,或者在开发环境使用代理。
- 当接口变多后,把 axios 封装成统一的 request 模块。
- 按业务拆分 API 文件,页面只调用方法,不直接拼接口地址。
这篇旧文当年只是为了解决“前端怎么拿到后端数据”这个问题。现在重新整理后,它更像是一条前后端分离开发的入门路径:先跑通,再封装,最后处理跨域和工程结构。
原文记录:vue请求后端数据和跨域问题