摘要
以React+Zustand+React Router为三驾马车,构建api层统一管理接口,vite-plugin-mock模拟数据,axios baseURL一键切换,实现前端独立于后端并行开发。
前后端分离的架构中,存在一个结构性矛盾:前端页面需要后端接口数据才能渲染,而后端接口往往滞后于前端开发进度。传统模式下,前端开发者只能"傻等"------UI 写完了,数据接不上,联调阶段才发现接口格式对不上,返工成本极高。
前端 API 工程化的思路是:在后端接口就绪之前,前端自建一套完整的接口层,模拟真实 API 的响应格式,让前端应用可以独立运行、独立调试。后端接口完成后,只需切换一个 baseURL,前端代码无需任何改动。
前端独立开发的三驾马车
一个前端应用可以从三个维度解构:组件 (UI 层)、路由 (页面导航)、状态管理(数据层)。React 生态为每个维度提供了成熟的方案:
json
{
"dependencies": {
"react": "^19.2.6",
"react-dom": "^19.2.6",
"react-router-dom": "^7.18.2",
"zustand": "^5.0.14",
"axios": "^1.19.0"
},
"devDependencies": {
"vite": "^8.0.12",
"vite-plugin-mock": "^3.0.2"
}
}
- React 负责组件化 UI 与响应式渲染,
createRoot挂载根组件。 - React Router 负责前端路由,
BrowserRouter+Routes+Route定义页面级导航。 - Zustand 负责全局状态管理,比 Redux 更轻量,比 Context 更适合跨组件共享。
三者配合,前端已经具备了完整的独立运行能力------唯一缺失的环节是数据来源。
前端接口层:api/ 目录的职责
传统的做法是组件直接调用 fetch 或 axios 请求后端接口。但这样做会把"接口地址"和"数据格式"硬编码在组件中,形成对后端的强耦合。
前端接口层的核心思想是将接口调用抽象为独立的模块 ,统一放在 api/ 目录下。每个接口对应一个函数,组件只调用函数,不关心数据来源。
config.js ------ axios 实例化配置:
javascript
import axios from 'axios';
const instance = axios.create({
baseURL: '/api',
timeout: 5000,
});
export default instance;
baseURL: '/api' 是整条链路的关键。所有接口请求自动拼接 /api 前缀,由 Vite 开发服务器代理或 mock 插件拦截处理。切换到真实后端时,只需将 baseURL 改为 http://localhost:3000,所有接口调用无需修改。
api/todos.js ------ 接口模块:
javascript
import axios from './config';
export const getTodos = async () => {
const res = await axios.get('/todos');
return res.data;
};
getTodos 封装了"获取待办列表"这个接口。调用方只需要 await getTodos(),不需要知道请求方法、URL 路径、数据格式。这种封装带来了三个好处:接口变更有单一修改点,组件与网络层解耦,以及 mock 与真实接口可以无缝切换。
Mock 数据:vite-plugin-mock 让前端独立运行
Mock 是前端独立开发的关键基础设施。vite-plugin-mock 在 Vite 开发服务器中注册一个中间件,拦截匹配 /api/* 的请求,返回预定义的 JSON 数据------整个过程对前端代码完全透明。
vite.config.js 配置:
javascript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { viteMockServe } from 'vite-plugin-mock';
export default defineConfig({
plugins: [
react(),
viteMockServe({
mockPath: 'mock',
localEnabled: true,
}),
],
});
mockPath: 'mock' 指定 mock 文件目录,localEnabled: true 在开发环境启用。Vite 启动后,/api/todos 的请求会被 mock 插件拦截,返回 mock 目录中定义的响应数据,不需要任何后端服务。
mock/ 目录下的 mock 文件定义请求与响应的映射关系:
javascript
export default [
{
url: '/api/todos',
method: 'get',
response: () => ({
todos: [
{ id: 1, title: '学习 React', completed: false },
{ id: 2, title: '学习 Zustand', completed: true },
]
}),
},
];
url 和 method 精确匹配请求,response 返回的 JSON 结构与真实后端保持一致。前端代码完全不知道数据来自 mock------它只看到 axios.get('/todos') 返回了正确的数据。
页面组件:从接口到 UI 的数据流
Todo 页面组件通过 useEffect 在挂载时调用接口,将返回的数据存入 useState:
javascript
import { getTodos } from '../api/todos';
import { useEffect, useState } from 'react';
function Todos() {
const [todos, setTodos] = useState([]);
useEffect(() => {
(async () => {
const data = await getTodos();
setTodos(data);
})();
}, []);
return (
<>
<h2>Todos</h2>
{/* todos 渲染 */}
</>
);
}
useEffect 的回调不能直接是 async 函数(React 要求 effect 回调返回 undefined 或清理函数),所以用 IIFE(立即执行函数表达式)包装异步逻辑。这个模式在数据获取场景中非常常见。
getTodos() 来自 api/todos.js,而 api/todos.js 的 axios 实例来自 config.js,config.js 的 baseURL 指向 /api。数据从 mock 插件 → axios 实例 → api 模块 → 页面组件,形成一条完整的、可追踪的数据流。
路由串联:App.jsx 的整体架构
javascript
import { Routes, Route, BrowserRouter as Router } from 'react-router-dom';
import Nav from './components/Nav';
import { lazy, Suspense } from 'react';
const Todos = lazy(() => import('./pages/Todos'));
const Home = lazy(() => import('./pages/Home'));
function App() {
return (
<Router>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/todos" element={<Todos />} />
</Routes>
</Router>
);
}
BrowserRouter 作为路由容器包裹整个应用,Nav 组件通过 Link 提供导航,Routes + Route 定义路径与页面的映射关系。React.lazy + Suspense 实现按需加载------Todos 和 Home 组件只在首次访问对应路由时才加载 JS 文件,减小首屏体积。
从 Mock 切换到真实后端
当后端接口开发完成后,切换过程只需要修改一个文件:
javascript
// config.js ------ 切换前
const instance = axios.create({
baseURL: '/api',
timeout: 5000,
});
// config.js ------ 切换后
const instance = axios.create({
baseURL: 'http://localhost:3000',
timeout: 5000,
});
所有 api/todos.js、api/xxx.js 以及调用它们的组件代码完全不需要改动。这就是前端接口层抽象的核心价值------将"接口地址"这一易变因素隔离在唯一的配置文件中。
如果后端接口的路径也与 mock 不同(比如 mock 是 /api/todos,后端是 /todos),可以在 config.js 中通过 baseURL 的差异来抹平,或者使用 Vite 的 server.proxy 配置代理转发。
前端工程化的完整视图
scss
┌─────────────────────────────────────┐
│ 页面层 (pages/) │
│ Home.jsx Todo.jsx │
│ 调用 api/ 模块获取数据 │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ 接口层 (api/) │
│ config.js (axios 实例, baseURL) │
│ todos.js (getTodos 等接口函数) │
│ 统一管理所有后端接口调用 │
└──────────────┬──────────────────────┘
│
┌─────────┴─────────┐
│ │
┌────▼──────┐ ┌───────▼───────┐
│ Mock 层 │ │ 真实后端 │
│ vite- │ │ Koa/MySQL │
│ plugin- │ │ localhost: │
│ mock │ │ 3000 │
└───────────┘ └───────────────┘
三层架构中,页面层只关心"我需要什么数据",接口层只关心"怎么获取数据",mock 层和真实后端是接口层的两个可替换实现。每一层职责单一,边界清晰。
总结
前端 API 工程化解决的核心问题是前后端开发节奏不同步 。通过在 api/ 目录下统一管理接口调用、用 axios.create 集中配置 baseURL、用 vite-plugin-mock 模拟后端响应,前端可以在后端接口就绪之前独立完成全部开发和调试。
React Router 管理页面路由,Zustand 管理全局状态,axios + mock 管理数据接口------三个维度各自独立又协同工作,构成了现代前端工程化的基础骨架。后端接口就绪后,一行 baseURL 的修改即可完成切换,零侵入,零风险。