在前面的内容里,我们已经分别学过两件很重要的事:
- 用
fetch / Ajax主动向服务器发请求 - 用 Vue 3 的
ref、v-for、v-if、onMounted管理页面和数据
但真正进入 Vue 项目后,初学者最常见的断层恰恰发生在这里:
单独看
fetch会,单独看 Vue 也会,可一旦把接口请求放进 Vue 组件里,就不知道数据该放哪、请求该什么时候发、加载状态怎么写、错误提示怎么处理。
这篇文章就是专门解决这个问题的。
本文不会一上来就讲拦截器、请求实例封装、接口模块化管理,而是先把最核心的一步走稳:
在 Vue 组件里,使用
axios请求接口,并把返回数据渲染到页面上。
1. 为什么 Vue 项目里常配合 Axios
在浏览器里,请求接口可以直接使用原生 fetch。但在 Vue 工程项目中,很多团队更常使用 axios,原因通常有三个:
- 写法简洁:返回数据通常直接从
res.data读取。 - 错误处理更直接:非
2xx状态码通常会直接进入catch。 - 更适合后续工程化升级:请求拦截器、响应拦截器、统一
baseURL、统一错误提示都更容易接入。
所以对 Vue 项目的学习顺序来说,比较自然的路线是:
- 先理解浏览器原生
fetch - 再学习
axios在组件里的基础用法 - 最后再学习
axios封装与接口模块化管理
2. 先安装 Axios
如果你已经有一个基于 Vite + Vue 3 的项目,可以直接安装:
npm install axios
安装完成后,就可以在组件里这样引入:
import axios from "axios";
3. 第一个请求:组件挂载后加载列表
在 Vue 组件中,请求接口最常见的时机就是 onMounted()。
因为这个钩子表示:
组件已经挂载到页面上,可以开始做“页面初始化工作”了。
3.1 最小示例
<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";
// 存放接口返回的用户列表
const users = ref([]);
async function getUserList() {
// 发送 GET 请求获取列表
const res = await axios.get("https://jsonplaceholder.typicode.com/users");
// 把结果写回响应式状态
users.value = res.data;
}
onMounted(() => {
// 页面首次加载时拉取数据
getUserList();
});
</script>
<template>
<ul>
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
这段代码的流程非常重要:
- 用
ref([])定义一个响应式数组 - 在
getUserList()中发起请求 - 把接口返回的数据赋值给
users.value - 模板通过
v-for自动渲染列表
这就是 Vue 项目里最基本的数据流:
请求数据 -> 保存到响应式状态 -> 模板自动更新
4. 为什么数据要放进 ref
很多初学者第一次把请求结果写进 Vue 组件时,容易这样写:
let users = [];
然后请求成功后:
users = res.data;
这样写在普通 JavaScript 中当然没有问题,但在 Vue 里,页面并不会因为这个普通变量变化就自动更新。
正确思路是:
const users = ref([]);
users.value = res.data;
因为 Vue 需要通过响应式系统感知数据变化,ref 正是为了这个目的而存在的。
5. 把页面状态写完整:加载中、空状态、错误状态
真实项目里,请求接口不能只考虑“成功”一种情况。
一个更完整的组件,至少要考虑这四种状态:
- 初始状态
- 加载中
- 加载成功
- 加载失败
如果返回的是列表,还要考虑:
- 数据为空
5.1 一个更完整的列表组件
<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";
const users = ref([]);
const loading = ref(false);
const errorMsg = ref("");
async function getUserList() {
// 请求开始前先切到加载态
loading.value = true;
errorMsg.value = "";
try {
const res = await axios.get("https://jsonplaceholder.typicode.com/users");
users.value = res.data;
} catch (error) {
// 请求失败时记录错误信息
errorMsg.value = error.message || "请求失败";
} finally {
// 无论成功还是失败,都结束加载状态
loading.value = false;
}
}
onMounted(() => {
getUserList();
});
</script>
<template>
<div>
<p v-if="loading">加载中...</p>
<p v-else-if="errorMsg">{{ errorMsg }}</p>
<p v-else-if="users.length === 0">暂无数据</p>
<ul v-else>
<li v-for="user in users" :key="user.id">
{{ user.name }} - {{ user.email }}
</li>
</ul>
</div>
</template>
这里最值得建立的意识是:
loading控制加载中errorMsg控制失败提示users.length === 0控制空状态users真正负责业务数据
这些状态拆分清楚后,组件逻辑就会非常稳定。
6. 搜索与重新请求:不是只在 onMounted 里发一次
很多页面并不是只在首次进入时请求一次,还会有:
- 搜索
- 筛选
- 切换页码
- 手动刷新
这时要记住一个重要原则:
onMounted()只是“首次触发入口”,真正发请求的逻辑应该抽成独立函数。
6.1 搜索示例
<script setup>
import { ref, onMounted } from "vue";
import axios from "axios";
const keyword = ref("");
const posts = ref([]);
const loading = ref(false);
async function getPostList() {
loading.value = true;
try {
const res = await axios.get("https://jsonplaceholder.typicode.com/posts");
const list = res.data;
// 根据关键字过滤标题
posts.value = list.filter(item =>
item.title.includes(keyword.value.trim())
);
} finally {
loading.value = false;
}
}
function handleSearch() {
// 点击搜索时复用同一套请求逻辑
getPostList();
}
onMounted(() => {
getPostList();
});
</script>
<template>
<div>
<input v-model="keyword" placeholder="请输入关键字" />
<button @click="handleSearch">搜索</button>
<p v-if="loading">加载中...</p>
<ul v-else>
<li v-for="post in posts" :key="post.id">
{{ post.title }}
</li>
</ul>
</div>
</template>
这里的重点不是搜索逻辑本身,而是建立一个组件设计习惯:
- 请求函数独立出来
- 页面事件只负责调用请求函数
- 初始化和后续刷新复用同一套逻辑
7. 提交表单:POST 请求怎么写
除了获取列表,Vue 页面里另一个高频操作就是提交表单。
例如注册、登录、新增文章、发布评论,本质上都是 POST 请求。
7.1 一个最小登录示例
<script setup>
import { reactive, ref } from "vue";
import axios from "axios";
// 表单数据适合用 reactive 统一管理
const formData = reactive({
username: "",
password: ""
});
const loading = ref(false);
const resultText = ref("");
async function handleSubmit() {
loading.value = true;
resultText.value = "";
try {
// 提交登录表单
const res = await axios.post("https://api.example.com/login", {
username: formData.username,
password: formData.password
});
resultText.value = "提交成功";
console.log("服务器返回:", res.data);
} catch (error) {
resultText.value = error.message || "提交失败";
} finally {
loading.value = false;
}
}
</script>
<template>
<div>
<input v-model="formData.username" placeholder="用户名" />
<input v-model="formData.password" type="password" placeholder="密码" />
<button :disabled="loading" @click="handleSubmit">
{{ loading ? "提交中..." : "登录" }}
</button>
<p>{{ resultText }}</p>
</div>
</template>
这里体现了几个常见配合方式:
- 表单数据适合用
reactive - 按钮加载状态适合用
ref - 请求完成后根据结果更新页面提示
8. Vue 页面里最常见的 Axios 写法
在企业项目中,一个普通的列表页经常会长成这样:
<script setup>
import { ref, reactive, onMounted } from "vue";
import axios from "axios";
const loading = ref(false);
const total = ref(0);
const tableData = ref([]);
// 统一维护列表筛选和分页参数
const queryParams = reactive({
page: 1,
pageSize: 10,
keyword: ""
});
async function getTableList() {
loading.value = true;
try {
// GET 请求里的查询条件放到 params 中
const res = await axios.get("https://api.example.com/users", {
params: queryParams
});
tableData.value = res.data.list;
total.value = res.data.total;
} catch (error) {
console.error("获取表格数据失败:", error);
} finally {
loading.value = false;
}
}
function handleSearch() {
// 搜索时通常要重置回第一页
queryParams.page = 1;
getTableList();
}
function handlePageChange(newPage) {
// 翻页时更新当前页并重新请求
queryParams.page = newPage;
getTableList();
}
onMounted(() => {
getTableList();
});
</script>
这个结构非常值得记住,因为它几乎就是后台管理页面的基础模板:
queryParams存筛选条件tableData存列表数据total存总数loading控制加载态getTableList()统一负责请求
你后面学 Element Plus 表格、分页、搜索表单时,会反复看到这种组织方式。
9. 常见请求配置:params、data、headers
9.1 params
用于 GET 请求的查询参数:
axios.get("/users", {
params: {
page: 1,
keyword: "vue"
}
});
最终会变成类似:
/users?page=1&keyword=vue
9.2 data
用于 POST、PUT 等请求体数据:
axios.post("/users", {
username: "alice",
password: "123456"
});
9.3 headers
用于附加请求头:
axios.get("/profile", {
headers: {
Authorization: "Bearer your-token"
}
});
在当前阶段,你只需要先知道它们各自的职责。后面讲 Axios 封装 时,再把这些配置统一接管起来。
10. 初学者最容易踩的坑
10.1 忘记给响应式变量赋 .value
users = res.data; // 错误
users.value = res.data; // 正确
10.2 请求写在模板外,但没有在生命周期里触发
如果没有在 onMounted() 或点击事件里调用,请求函数根本不会执行。
10.3 接口返回成功,但页面没显示
常见原因:
- 数据没有赋值给响应式变量
v-for绑定字段名写错- 返回数据结构和你想象的不一样
这时第一件事永远是:
console.log(res.data);
10.4 只写成功分支,不写失败分支
页面一旦网络异常,就会直接白掉,用户完全不知道发生了什么。
10.5 把所有请求逻辑都塞进模板组件里
入门阶段这样写可以接受,但一旦页面变复杂,就要开始考虑:
- 请求函数拆分
- 复用逻辑抽离
- 接口统一管理
这也正是后面学习 Axios 封装 的原因。
11. 当前这篇解决了什么,下一篇又解决什么
这篇文章重点解决的是:
- Vue 组件里怎么发请求
- 请求结果怎么进响应式状态
- 列表和表单怎么和接口联动
- 加载、错误、空状态怎么写
但真实项目里,下面这些问题我们还没解决:
baseURL每次都手写吗token每次都手动加到请求头吗401失效要不要统一跳登录- 接口路径要不要统一放到
api/目录 - 错误提示要不要统一处理
这些就属于更高一层的工程化问题。
12. 小结
把 axios 接进 Vue 组件后,你就真正迈进了“页面连上真实数据”的阶段。
最核心的心智模型只有一句话:
用
axios获取数据,用 Vue 的响应式状态保存数据,再用模板把状态渲染成页面。
一旦这个链路打通,后面学表格页、搜索页、表单页、后台管理系统,都会顺很多。
13. 下篇可以学什么
最适合接在这一篇之后的主题,就是:
13.1 Axios 封装与 API 管理
继续学习:
- 创建统一请求实例
- 配置
baseURL - 请求拦截器与响应拦截器
- Token 自动注入
- 统一错误处理
api/目录模块化管理
13.2 Vue Router 与 Pinia
继续学习:
- 页面跳转与路由配置
- 单页应用结构
- 全局状态管理
如果你想把请求从“能用”升级到“可维护”,建议下一篇优先学习 Axios 封装与 API 管理。