前后端分离开发,别再"傻等"后端接口了------聊聊前端接口工程
有个朋友之前做小组作业,跟我吐槽:"后端同学接口还没写,我前端页面都画好了,结果只能干坐着等接口,一等等三天。"
我问他:你为什么要等?
他说:没接口,我拿不到数据,页面就是死的啊。
这就是今天想聊的话题------前后端分离之后,前端到底怎么做到不等后端,也能把整个 App 独立写完。
一、先搞清楚一个问题:前后端分离,分离了吗?
现在几乎没人会把 HTML、CSS、JS 和后端代码写在一起了。前端是独立工程,后端也是独立工程,各自 npm run dev 起一个服务,看起来很"分离"。
但只要你真的写过联调,就会发现一个尴尬的事实:
前后端之间,还留着一根"脐带"------
/api接口。
前端要渲染数据,得调后端的接口;后端接口没写好,前端就"断粮"了。
更气人的是:前端画界面可能只需要半天,但等后端接口可能要等好几天。 这半天和好几天之间,前端就变成了"摸鱼时间"。
那能不能把这根脐带也剪断?
能。办法就是------前端自己做一层"接口工程",先用假数据(mock)把页面跑起来,等后端真接口好了,再"一键切换"过去。
这就是这个项目 todos-fullstack 想讲清楚的事。
二、前端独立开发的"三辆马车" + 一个关键补充
先看一个独立的前端应用,到底需要哪些东西。我的理解是"三辆马车":
| 马车 | 作用 | 对应技术 |
|---|---|---|
| 组件 | 响应式渲染界面 | React 19 |
| 路由 | 页面级别的跳转 | react-router-dom 7 |
| 状态管理 | 全局数据的"银行" | zustand 5 |
这三辆马车配齐了,前端就能独立跑起来。
但很多人漏掉了第四样东西------数据从哪来?
组件负责"画",路由负责"去哪",状态管理负责"存",那数据接口谁来管?
答案是:前端接口工程(API 工程) 。它不归 react-router 管,它是自己独立的一层。这就是这个项目里 src/api/ 目录存在的意义。
bash
todos-fullstack/
├── backend/ # 后端:node + koa + mysql(真正的接口)
└── frontend/todos/
├── mock/ # 前端造的"假接口"
├── src/
│ ├── api/ # ⭐ 前端接口工程
│ │ ├── config.js # axios 配置(baseURL 一键切换)
│ │ └── todos.js # 每个模块一个接口文件
│ ├── pages/ # 页面级路由
│ ├── components/ # 通用组件
│ └── App.jsx
└── vite.config.js
三、核心:用 mock 把"假接口"接上
思路很简单:后端没给真接口,前端就自己伪造一个同路径的假接口。
这里用的是 vite-plugin-mock。在 vite.config.js 里加一段配置:
js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [
react(),
viteMockServe({
mockPath: './mock', // mock 文件放哪
localEnabled: true, // 本地开发开启
}),
],
})
然后在 mock/ 目录写假接口。注意:路径要和后端约定的一致 ,这里就是 /api/todos:
js
// mock/todos.js
export default [
{
url: '/api/todos',
method: 'GET',
timeout: 2000, // 故意模拟网络延迟
response: () => {
return {
code: 0, // 0 表示成功
todos: [
{ id: 1, title: '学习前端接口工程', completed: true },
{ id: 2, title: '看龙餐馆', completed: false },
],
}
},
},
]
现在你在浏览器访问 http://localhost:5173/api/todos,就能拿到这段假 JSON 了。前端"有粮"了。
四、前端接口工程:把"请求"统一管起来
光有 mock 还不够。如果每个组件里都直接 fetch('/api/todos'),等后端接口地址一变,你就要全局搜索替换,灾难。
所以要把请求收口到一层 。这就是 api/ 目录的职责。
第一步:配置 axios 实例,baseURL 统一管理。
js
// src/api/config.js
import axios from 'axios'
const instance = axios.create({
baseURL: '/api', // 开发环境:走前端 mock
// baseURL: 'http://localhost:3000', // 上线:改成后端真实地址
timeout: 5000,
})
export default instance
看到这行注释了吗?这就是"一键切换"的灵魂:
- 开发阶段
baseURL: '/api'→ 请求走 Vite 的 mock 插件 - 联调/上线时 → 改成
http://localhost:3000(后端 Koa 地址)
只改一行,全局生效。 再也不用每个请求去改地址。
第二步:每个业务模块一个接口文件。
js
// src/api/todos.js
import axios from './config'
export const getTodos = async () => {
const res = await axios.get('/todos')
return res.data
}
这样组件里调用就非常干净,只关心业务,不关心地址:
jsx
// src/pages/Todos.jsx
import { useState, useEffect } from 'react'
import { getTodos } from '../api/todos'
function Todos() {
const [todos, setTodos] = useState([])
useEffect(() => {
(async () => {
const data = await getTodos()
setTodos(data) // ⚠️ 注意:这里 data 是响应体 { code, todos },实际要取 data.todos
})()
}, [])
return <>Todos</>
}
顺便说个容易被忽略的细节:mock 返回的结构是
{ code: 0, todos: [...] },所以data其实是整个响应体,不是数组。约定好响应结构(比如统一code字段)后,前端取值的地方要记得data.todos。 这是很多新手联调时会懵一下的小坑。
五、前后端切换,到底切的是啥?
现在回头看整个流程,就很清楚了:
text
开发阶段(前端独立)
组件 → getTodos() → axios baseURL=/api → vite-plugin-mock 拦截 → 返回假数据
联调/上线阶段(切后端)
组件 → getTodos() → axios baseURL=http://localhost:3000 → Koa 真接口 → 返回真数据
组件代码一行不用改。 变的只是 config.js 里的一行 baseURL。
这就是"前端接口工程"的价值:把「数据从哪来」这个易变的东西,和「界面长什么样」这个稳定的东西,彻底解耦。
六、总结
回到最开始那个问题:前端为什么要傻等后端?
答案是不用等。你可以:
- 用 mock 伪造假接口,先让页面跑起来;
- 用
api/目录统一管理请求 ,别让fetch散落在各个组件里; - 用
baseURL一键切换,后端好了切过去,全程只改一行。
这背后其实是一种心态的转变:前后端分离,不光是"文件分开",更是"进度解耦"。 前端不是后端的附庸,前端自己也能是一套完整的工程。
写在最后
这篇文章里的 todos-fullstack 是个教学脚手架,重点是讲清楚「前端接口工程」这个思路,所以 Zustand 这辆"马车"目前只上了车、还没真正开动(状态还停在组件内的 useState)。如果你感兴趣,下一篇我可以接着聊聊:当 todo 列表复杂起来,怎么把状态从 useState 搬到 Zustand 这个"银行"里。
你在做前后端分离时,是"傻等接口"派,还是"mock 先行"派?
欢迎在评论区聊聊你的经历,特别是被联调卡过进度的血泪史 👇