React Todos 全栈项目入门:Vite、路由、Axios 与 Mock 请求链路
- 前言
- [1. Axios 基础知识](#1. Axios 基础知识)
-
- [1.1 Axios 是干什么的](#1.1 Axios 是干什么的)
- [1.2 Axios 实例是什么](#1.2 Axios 实例是什么)
- [1.3 Axios 常用配置参数](#1.3 Axios 常用配置参数)
-
- [1.3.1 baseURL 公共地址前缀](#1.3.1 baseURL 公共地址前缀)
- [1.3.2 timeout 超时时间](#1.3.2 timeout 超时时间)
- [1.3.3 headers 请求头](#1.3.3 headers 请求头)
- [1.3.4 params 查询参数](#1.3.4 params 查询参数)
- [1.3.5 Cookie 与 withCredentials](#1.3.5 Cookie 与 withCredentials)
- [2. Vite 项目架构与依赖安装](#2. Vite 项目架构与依赖安装)
-
- [2.1 Vite 在项目中起什么作用](#2.1 Vite 在项目中起什么作用)
- [2.2 路由、Axios 与 Mock 依赖](#2.2 路由、Axios 与 Mock 依赖)
- [3. 项目目录与各模块职责](#3. 项目目录与各模块职责)
-
- [3.1 项目结构](#3.1 项目结构)
- [3.2 index.html 与 main.jsx:应用如何启动](#3.2 index.html 与 main.jsx:应用如何启动)
- [4. React Router 页面路由](#4. React Router 页面路由)
-
- [4.1 App.jsx 路由中心](#4.1 App.jsx 路由中心)
- [4.2 Nav 和 Route 为什么都需要](#4.2 Nav 和 Route 为什么都需要)
- [5. 项目中的 Axios API 层](#5. 项目中的 Axios API 层)
-
- [5.1 config.js:统一配置怎么请求](#5.1 config.js:统一配置怎么请求)
- [5.2 api/todos.js:定义请求什么数据](#5.2 api/todos.js:定义请求什么数据)
- [6. Vite Mock 模拟接口](#6. Vite Mock 模拟接口)
-
- [6.1 vite.config.js 注册 Mock 插件](#6.1 vite.config.js 注册 Mock 插件)
- [6.2 mock/todos.js 返回模拟数据](#6.2 mock/todos.js 返回模拟数据)
- [7. Todo.jsx 与完整数据链路](#7. Todo.jsx 与完整数据链路)
-
- [7.1 Todo.jsx 进入页面后做什么](#7.1 Todo.jsx 进入页面后做什么)
- [7.2 data 到底来自哪里](#7.2 data 到底来自哪里)
- [7.3 请求消息如何走完整个项目](#7.3 请求消息如何走完整个项目)
- [8. 当前全栈项目处于什么阶段](#8. 当前全栈项目处于什么阶段)
- 总结
前言
一个基础的 Todos 项目,已经包含了前端工程中非常重要的几部分:Vite 负责启动开发环境,React 负责渲染页面,React Router 负责切换页面,Axios 负责发送 HTTP 请求,Vite Mock 则在后端尚未完成时提供模拟数据。
理解这个项目的关键,不是单独记住某一行语法,而是弄清楚各个模块分别负责什么,以及它们如何连接起来:
text
进入 /todos 页面
→ Todo 组件开始执行
→ 调用 getTodos()
→ Axios 发送 GET /api/todos
→ Vite Mock 匹配这个请求
→ Mock 返回 Todo 数据
→ Axios 接收响应
→ Todo 组件拿到 data
本文先讲 Axios 的基础知识,再介绍 Vite 项目架构、目录组成、页面路由、API 层和 Mock 层,最后把整条请求过程串起来。
1. Axios 基础知识
1.1 Axios 是干什么的
Axios 是一个 HTTP 请求库,用来帮助前端向服务器发送请求,并接收服务器返回的数据。
Axios 运行在浏览器中的 JavaScript 代码里。真正提供网络能力的是浏览器,Axios 在浏览器网络能力的基础上封装了一套更方便的调用方式。
例如,前端需要获取 Todo 数据,可以发送一个 GET 请求:
js
axios.get('/api/todos')
前端需要提交一条新任务,可以发送一个 POST 请求:
js
axios.post('/api/todos', {
title: '学习 Axios',
completed: false,
})
常见 HTTP 请求可以这样理解:
| Axios 调用 | HTTP 方法 | 常见作用 |
|---|---|---|
axios.get() |
GET | 查询数据 |
axios.post() |
POST | 新增数据 |
axios.put() |
PUT | 修改完整数据 |
axios.patch() |
PATCH | 修改部分数据 |
axios.delete() |
DELETE | 删除数据 |
Axios 只负责"发送请求和接收响应",它不负责页面跳转,也不负责把数据渲染到页面上。页面跳转由 React Router 负责,页面渲染由 React 负责。
1.2 Axios 实例是什么
直接使用 Axios 时,可以这样写:
js
axios.get('/api/todos')
axios.get('/api/users')
当项目中的请求越来越多时,很多公共配置会重复出现。因此 Axios 提供了 axios.create(),用来创建一个提前配置好的 Axios 实例:
js
const instance = axios.create({
baseURL: '/api',
timeout: 5000,
})
创建实例后,可以使用这个实例发送请求:
js
instance.get('/todos')
instance.get('/users')
这两个请求会自动使用实例中的公共配置。
Axios 实例可以理解为一个已经设置好公共规则的请求工具。
1.3 Axios 常用配置参数
Axios 实例可以接收很多配置,其中最常用的是 baseURL、timeout、headers 和 withCredentials。查询参数 params 通常写在某一次具体请求中。
| 参数 | 作用 | 示例 |
|---|---|---|
baseURL |
给所有相对请求添加公共地址前缀 | /api |
timeout |
设置请求最长等待时间,单位是毫秒 | 5000 |
headers |
设置请求头 | Content-Type、Authorization |
withCredentials |
跨域请求时是否携带 Cookie | true 或 false |
params |
设置 URL 查询参数 | page=1、completed=true |
data |
设置请求体数据 | 新增或修改时提交的对象 |
1.3.1 baseURL 公共地址前缀
假设 Axios 实例配置为:
js
const instance = axios.create({
baseURL: '/api',
})
发送请求时只写:
js
instance.get('/todos')
Axios 会把两部分组合起来:
text
/api + /todos = /api/todos
因此浏览器最终请求的是:
text
GET /api/todos
使用公共前缀有两个主要作用:
- 不需要在每个请求中重复编写
/api。 - 公共地址变化时,只需要修改 Axios 实例中的一处配置。
需要注意,/api 本身不会自动连接后端。它只是一个普通的路径前缀。请求最终由 Mock、Vite 代理还是真实后端处理,要看服务器如何配置。
1.3.2 timeout 超时时间
js
const instance = axios.create({
timeout: 5000,
})
5000 的单位是毫秒,表示请求最多等待 5 秒。如果 5 秒后仍然没有收到响应,Axios 会结束等待并产生超时错误。
1.3.3 headers 请求头
请求头用来向服务器补充说明请求信息:
js
const instance = axios.create({
headers: {
'Content-Type': 'application/json',
},
})
Content-Type: application/json 表示请求体中的数据采用 JSON 格式。登录系统中还可能通过 Authorization 请求头携带令牌。
1.3.4 params 查询参数
查询参数用来告诉服务器需要查询什么条件的数据:
js
instance.get('/todos', {
params: {
completed: true,
page: 1,
},
})
Axios 会将它转换为:
text
/api/todos?completed=true&page=1
其中,completed=true 可以表示只查询已完成任务,page=1 可以表示查询第一页。查询参数经常用于搜索、筛选、排序和分页。
1.3.5 Cookie 与 withCredentials
Cookie 是浏览器为某个网站保存的一小段数据,常用于保存登录会话标识。用户登录成功后,服务器可以让浏览器保存一个会话标识;以后浏览器再次请求服务器时,会携带这个 Cookie,服务器就能判断当前用户是谁。
当前端和后端跨域,并且登录状态依赖 Cookie 时,可以配置:
js
const instance = axios.create({
withCredentials: true,
})
是否需要开启它,要看项目是否使用 Cookie 登录,以及后端是否允许跨域携带凭证。
2. Vite 项目架构与依赖安装
2.1 Vite 在项目中起什么作用
Vite 是这个前端项目的开发服务器和构建工具。开发阶段执行 pnpm dev 后,Vite 会启动本地服务、处理 JavaScript 模块、转换 React JSX,并在代码变化后更新页面。
创建 React + Vite 项目可以使用:
bash
pnpm create vite todos --template react
cd todos
pnpm i
pnpm dev
这些命令的作用如下:
| 命令 | 作用 |
|---|---|
pnpm create vite todos --template react |
创建名为 todos 的 React + Vite 项目 |
cd todos |
进入项目目录 |
pnpm i |
安装 package.json 中声明的依赖 |
pnpm dev |
启动 Vite 开发服务器 |
2.2 路由、Axios 与 Mock 依赖
在基础项目之上,需要安装路由、请求和 Mock 相关依赖:
bash
pnpm i react-router-dom
pnpm i axios
pnpm i -D vite-plugin-mock
| 依赖 | 作用 | 类型 |
|---|---|---|
react-router-dom |
管理 React 页面路由 | 运行依赖 |
axios |
发送 HTTP 请求 | 运行依赖 |
vite-plugin-mock |
在开发阶段模拟后端接口 | 开发依赖 |
-D 表示将依赖安装到 devDependencies。Mock 插件主要在开发阶段使用,所以通过 pnpm i -D vite-plugin-mock 安装。
安装 vite-plugin-mock 后,还需要在 vite.config.js 中引入并注册它。安装表示项目拥有了这个插件,注册表示 Vite 启动时真正使用这个插件。
3. 项目目录与各模块职责
3.1 项目结构
text
todos-fullstack/
├── backend/
│ └── package.json
└── frontend/
└── todos/
├── mock/
│ └── todos.js
├── src/
│ ├── api/
│ │ ├── config.js
│ │ └── todos.js
│ ├── components/
│ │ └── Nav.jsx
│ ├── pages/
│ │ ├── Home.jsx
│ │ └── Todo.jsx
│ ├── App.jsx
│ ├── index.css
│ └── main.jsx
├── index.html
├── package.json
└── vite.config.js
各个路径的职责如下:
| 路径 | 作用 |
|---|---|
index.html |
浏览器加载的 HTML 入口,提供 React 挂载节点 |
src/main.jsx |
创建 React 根节点并渲染 App |
src/App.jsx |
配置整个应用的页面路由 |
src/components/Nav.jsx |
提供 Home 和 Todos 导航链接 |
src/pages/Home.jsx |
首页组件 |
src/pages/Todo.jsx |
Todo 页面组件,进入页面后请求数据 |
src/api/config.js |
创建 Axios 实例,保存公共请求配置 |
src/api/todos.js |
定义获取 Todo 数据的请求函数 |
mock/todos.js |
声明模拟的 GET /api/todos 接口和返回数据 |
vite.config.js |
注册 React 插件和 Mock 插件 |
package.json |
保存项目脚本和依赖信息 |
backend/package.json |
声明后端 Koa 依赖,目前后端业务尚未展开 |
整个前端可以划分为三层:
| 层次 | 相关路径 | 主要职责 |
|---|---|---|
| 页面层 | pages、components、App.jsx |
页面展示、路由和组件交互 |
| API 层 | src/api |
发送请求并把响应交给页面 |
| Mock 层 | mock、vite.config.js |
在开发阶段模拟服务器响应 |
3.2 index.html 与 main.jsx:应用如何启动
index.html 中最关键的部分是:
html
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
#root 是 React 的挂载位置,浏览器接着加载 main.jsx:
js
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
createRoot() 找到页面中的 #root,render() 再把 <App /> 渲染进去。从这里开始,React 接管页面内容。
4. React Router 页面路由
4.1 App.jsx 路由中心
App.jsx 的代码如下:
js
import { lazy, Suspense } from 'react';
import { Routes, Route, BrowserRouter as Router } from 'react-router-dom';
import Nav from './components/Nav';
const Home = lazy(() => import('./pages/Home.jsx'));
const Todo = lazy(() => import('./pages/Todo.jsx'));
function App() {
return (
// 路由接管一切
<Router>
<Nav />
<Suspense fallback={<p>加载中...</p>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/todos" element={<Todo />} />
</Routes>
</Suspense>
</Router>
)
}
export default App
各部分的作用如下:
| 代码 | 作用 |
|---|---|
BrowserRouter as Router |
创建路由环境,并将 BrowserRouter 简写成 Router |
Routes |
根据当前浏览器路径查找匹配的路由 |
Route |
建立路径和页面组件之间的对应关系 |
lazy() |
需要显示页面时再加载对应组件 |
Suspense |
懒加载期间显示"加载中..." |
<Nav /> |
显示可以点击的导航区域 |
这两条 Route 建立了下面的对应关系:
| 浏览器路径 | 显示组件 |
|---|---|
/ |
Home |
/todos |
Todo |
4.2 Nav 和 Route 为什么都需要
Nav.jsx 的代码如下:
js
import { Link } from 'react-router-dom';
function Nav() {
return (
<nav style={{padding:10, borderBottom:'1px solid #ccc'}}>
<Link to="/">Home</Link>
<Link to="/todos">Todos</Link>
</nav>
)
}
export default Nav
Nav 和 Route 负责不同的工作:
| 对象 | 负责什么 |
|---|---|
Nav 中的 Link |
提供可以点击的入口,并修改浏览器路径 |
Route |
路径变化后,决定显示哪个组件 |
点击 Todos 后,执行过程是:
text
点击 <Link to="/todos">
→ 浏览器路径变成 /todos
→ Router 感知路径变化
→ Routes 重新匹配
→ Route 匹配 path="/todos"
→ 页面显示 Todo 组件
即使删除 <Nav />,在地址栏直接输入 /todos,Route 仍然可以匹配 Todo 页面。但是页面上将失去可点击的导航入口。
5. 项目中的 Axios API 层
5.1 config.js:统一配置怎么请求
src/api/config.js 的代码如下:
js
//配置下axios
import axios from 'axios';
//实例化axios
//fetch 缺点是功能小
//app /api/todos->:3000/todos
//统一管理,fetch 升级为axios
const instance=axios.create({
baseURL:'/api',//dev 前端模拟的请求地址 /api/todos
//baseURL:'http://localhost:3300',//prod 前端模拟的请求地址 /api/todos
timeout:5000,//超时时间
})
export default instance;
这个模块做了两件事:
- 通过
axios.create()创建 Axios 实例。 - 将这个实例默认导出,供其他 API 模块使用。
当前实例只有两个配置:
| 配置 | 当前值 | 实际作用 |
|---|---|---|
baseURL |
/api |
给相对请求添加 /api 前缀 |
timeout |
5000 |
最多等待 5 秒 |
因此,其他模块使用这个实例请求 /todos 时,最终请求路径会变成 /api/todos。
5.2 api/todos.js:定义请求什么数据
src/api/todos.js 的代码如下:
js
//一个模块一个js文件
import axios from './config';
//api 目录的职责 提供数据接口
//不是直接就去后端 后端没有开发好 和我们分离
export const getTodos=async()=>{
const res=await axios.get('/todos');
return res.data;
}
这里导入的变量虽然叫 axios,但它实际是 config.js 导出的 Axios 实例。
getTodos() 执行时:
text
axios.get('/todos')
→ 使用实例的 baseURL: '/api'
→ 最终发送 GET /api/todos
await 会等待请求完成。Axios 收到响应后,把完整响应对象赋给 res。res.data 是接口返回的业务数据,因此函数最后通过 return res.data 把数据交给调用它的页面。
两个 API 模块的区别可以概括为:
| 模块 | 解决的问题 |
|---|---|
config.js |
所有请求应该怎么发送 |
api/todos.js |
Todo 数据应该请求哪个路径 |
6. Vite Mock 模拟接口
6.1 vite.config.js 注册 Mock 插件
安装命令是:
bash
pnpm i -D vite-plugin-mock
当前 vite.config.js 的代码如下:
js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteMockServe } from 'vite-plugin-mock'
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), viteMockServe({
mockPath:'mock',
localEnabled: true,
})],
})
其中:
| 配置 | 作用 |
|---|---|
react() |
让 Vite 支持 React JSX 和热更新 |
viteMockServe() |
将 Mock 服务注册到 Vite |
mockPath: 'mock' |
告诉插件去 mock 目录读取模拟接口 |
localEnabled: true |
表示本地开发时启用 Mock |
Vite 启动后,Mock 插件会读取 mock 目录中的接口配置。当浏览器发送请求时,插件会检查请求方式和 URL 是否能够匹配。
6.2 mock/todos.js 返回模拟数据
当前 Mock 代码如下:
js
export default[
{
url:'/api/todos',
method:'get',
timeout:2000,
response:(req,res)=>{
return {
code:0,//成功,没问题
todos:[{
id:1,
title:'学习前端接口工程',
completed:true,
},
{
id:2,
title:'学习后端接口工程',
completed:false,
}
]
}
}
}
]
这段配置声明了一个模拟接口:
| 属性 | 当前值 | 作用 |
|---|---|---|
url |
/api/todos |
要匹配的请求路径 |
method |
get |
只匹配 GET 请求 |
timeout |
2000 |
延迟 2 秒返回数据 |
response |
函数 | 返回模拟的 Todo 数据 |
API 层和 Mock 层并不是通过相互导入连接起来的。它们依靠请求方式和请求路径建立联系:
text
API 层发送:GET /api/todos
Mock 层声明:GET /api/todos
两项都匹配后,Vite Mock 执行 response(),再把返回的数据作为 HTTP 响应交给 Axios。
7. Todo.jsx 与完整数据链路
7.1 Todo.jsx 进入页面后做什么
Todo.jsx 的代码如下:
js
import { getTodos } from '../api/todos';
import { useEffect, useState } from 'react';
function Todos(){
const [todos, setTodos]=useState([]);
useEffect(()=>{
//IIFE 立即执行函数
(async()=>{
const data=await getTodos();
setTodos(data);
})();
},[]);
return(
<>
Todos
</>
)
}
export default Todos
useState([]) 创建了一个状态:
todos保存 Todo 数据,初始值是空数组。setTodos用来修改todos。
useEffect(..., []) 会在 Todo 组件进入页面后执行。空数组 [] 表示这个 Effect 在组件首次挂载时运行。
Effect 内部的:
js
(async()=>{
const data=await getTodos();
setTodos(data);
})();
是一个异步立即执行函数。它刚被定义就会马上运行,在这个项目中的作用是:进入 /todos 页面后立即调用 getTodos() 请求初始 Todo 数据。
7.2 data 到底来自哪里
data 并不是自动出现的,它是 getTodos() 的返回值:
js
const data=await getTodos();
继续进入 getTodos():
js
const res=await axios.get('/todos');
return res.data;
Mock 的 response() 返回:
js
{
code: 0,
todos: [
{
id: 1,
title: '学习前端接口工程',
completed: true,
},
{
id: 2,
title: '学习后端接口工程',
completed: false,
},
],
}
Axios 接收到这份响应后,它会出现在 res.data 中。getTodos() 又执行了 return res.data,所以 Todo 页面中的 data 就是上面这个对象。
随后执行:
js
setTodos(data);
这会把整个对象保存到 todos 状态中。当前 JSX 只显示了文字 Todos,没有读取并遍历 todos,所以数据请求可以正常完成,但页面上暂时看不到返回的任务列表。
7.3 请求消息如何走完整个项目
完整过程如下:
text
1. 用户点击 Nav 中的 Todos
2. Link 把浏览器页面路径改成 /todos
3. Route 匹配 /todos 并显示 Todo.jsx
4. Todo 组件首次挂载,useEffect 开始执行
5. 异步立即执行函数调用 getTodos()
6. getTodos() 调用 Axios 实例的 get('/todos')
7. baseURL '/api' 与 '/todos' 组合成 /api/todos
8. 浏览器发送 GET /api/todos
9. Vite Mock 按照 GET 和 /api/todos 匹配接口
10. response() 返回模拟数据
11. Axios 把响应保存到 res
12. getTodos() 返回 res.data
13. Todo.jsx 中的 data 接到这个返回值
14. setTodos(data) 更新状态
这里存在两种不同的路径:
| 路径 | 处理者 | 作用 |
|---|---|---|
/todos |
React Router | 决定显示 Todo 页面 |
/api/todos |
Vite Mock | 返回 Todo JSON 数据 |
/todos 是页面路由,/api/todos 是数据接口。Axios 发送的是数据请求,不是页面跳转。
8. 当前全栈项目处于什么阶段
目前前端已经能够完成下面的闭环:
text
页面路由
→ Todo 组件
→ Axios API 层
→ Mock 接口
→ 返回模拟数据
→ 更新 React 状态
后端目录目前主要声明了 Koa 依赖,真正的 Koa 服务、Todo 路由和数据库逻辑还没有展开。因此当前阶段由 Vite Mock 临时承担数据接口的角色。
将来后端完成后,只要真实后端也能够接收对应请求并返回相同结构的数据,API 层就可以继续作为页面和后端之间的连接层。
总结
这个 Todos 项目由几块职责明确的模块组成:Vite 启动开发环境并加载插件,React Router 使用 /todos 控制页面显示,Axios 负责发送 HTTP 请求,config.js 保存公共请求配置,api/todos.js 定义 Todo 请求,Vite Mock 则通过 GET + /api/todos 匹配请求并返回模拟数据。进入 Todo 页面后,useEffect 中的异步立即执行函数调用 getTodos(),Axios 等待 Mock 响应,getTodos() 返回 res.data,最终由 setTodos(data) 保存到 React 状态。理解这些模块之间的职责边界,就能看清前端页面、请求层和模拟服务是如何组合成一条完整数据链路的。