从前后端分离到前端接口工程:React + MockJS + Vite 解析

从前后端分离到前端接口工程:React + MockJS + Vite 解析

前后端分离不是把代码放在不同文件夹就叫分离------真正关键是开发时序的解耦。本文通过一个 Todos 全栈项目,从底层讲透前端如何独立于后端完成开发闭环。


一、前后端分离的真正痛点

1.1 表面分离 vs 实际耦合

很多团队说"我们做了前后端分离",实际情况却是:

bash 复制代码
前端开发:我要一个 /api/todos 接口
后端开发:还没写好,等两天
前端开发:...那我等着吧

这就是时序耦合 ------虽然代码分开了,但前端开发进度仍然被后端卡脖子。前端页面写完了,数据接口没就绪,联调不了,测试不了,只能干等。

1.2 理想的状态

scss 复制代码
                    ┌─────────────────────┐
  前端独立开发       │  组件 + 路由 + 状态   │
  (第1天开始)        │  + Mock 接口层       │
                    └─────────────────────┘
                              │
                              │  后端就绪后
                              │  一键切换 baseURL
                              ▼
                    ┌─────────────────────┐
  前后端联调         │  真实 API 接入       │
  (后端完成后)       │  改一行配置即可       │
                    └─────────────────────┘

前端不应该等后端。这就是"前端接口工程"要解决的问题。


二、项目架构:三层分离

arduino 复制代码
todos-fullstack/
├── frontend/                    ← 前端:独立王国
│   └── todos/
│       ├── mock/                ← 模拟接口层
│       │   └── todos.js
│       ├── src/
│       │   ├── api/             ← 前端接口工程
│       │   │   ├── config.js    ← axios 配置 + baseURL
│       │   │   └── todos.js     ← 具体接口方法
│       │   ├── components/      ← 组件层
│       │   │   └── Nav.jsx
│       │   ├── pages/           ← 页面级路由
│       │   │   ├── Home.jsx
│       │   │   └── todos.jsx
│       │   └── App.jsx          ← 路由总入口
│       └── vite.config.js       ← Vite + Mock 插件
│
├── backend/                     ← 后端(Node + Koa + MySQL)
│   └── ...                      ← 独立开发,不影响前端
│
└── readme.md                    ← 架构笔记

三层泾渭分明

职责 独立性
mock/ 开发期数据源 后端不在时顶上去
api/ 统一请求管理 管配置、管请求、管切换
pages/ + components/ UI 渲染 + 状态 只跟 api 层交互,不关心数据从哪来

三、前端三驾马车:组件 + 路由 + 状态管理

readme 里点明了独立前端应用的三根支柱:

markdown 复制代码
组件 (响应式)  +  路由  +  状态管理
    ↓              ↓         ↓
  用户看到      页面跳转    数据流转

3.1 路由:React Router 接管一切

javascript 复制代码
// App.jsx
import { Routes, Route, BrowserRouter as Router } from 'react-router-dom';

function App() {
  return (
    <Router>
      <Nav />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/todos" element={<Todos />} />
      </Routes>
    </Router>
  )
}

<Router> 在最外层包裹整个应用,<Routes> 接管所有的页面级路由。<Nav> 组件放在 <Routes> 外面,意味着导航栏在所有页面都可见------这是路由布局的经典模式。

3.2 懒加载:优化首屏

ini 复制代码
const Home = lazy(() => import('./pages/Home'));
const Todos = lazy(() => import('./pages/todos'));

React.lazy() 配合动态 import() 实现了代码分割(Code Splitting)

makefile 复制代码
没做懒加载:  首屏加载所有代码 → 慢
做了懒加载:  首屏只加载当前页代码 → 快
            切换到 /todos 时再动态加载 Todos 组件

这是 Vite 底层 Rollup 打包的功劳,结合 React 的 Suspense 还能在加载时显示 fallback UI。


四、前端接口工程:api/ 目录设计

这是本文的核心------把接口管理提升为前端工程的一等公民

4.1 为什么需要 api/ 目录?

readme 给出了四个理由:

复制代码
① 后端接口往往不能及时提供
② 需要统一管理所有接口
③ 先用 mock 数据跑通流程
④ baseURL 一键切换开发/生产环境

4.2 第一层:axios 配置(config.js)

javascript 复制代码
// api/config.js
import axios from 'axios';

const instance = axios.create({
    baseURL: '/api',
    timeout: 5000,
});

export default instance;

这里没有直接使用 axios 全局实例,而是创建了一个独立实例

设计决策 原因
axios.create() 不污染全局 axios 默认配置,可以有多个独立实例(比如不同的后端服务)
baseURL: '/api' 所有请求自动拼接前缀。get('/todos') → 实际请求 /api/todos
timeout: 5000 5 秒超时保护,防止请求挂死

axios vs fetch 为什么选 axios?

bash 复制代码
fetch:                     axios:
  ❌ 需要手动 .json()        ✅ 自动 JSON 解析
  ❌ 请求超时需要 abort       ✅ 内置 timeout
  ❌ 拦截器需要包装           ✅ 请求/响应拦截器
  ❌ 取消请求复杂             ✅ CancelToken
  ❌ 进度事件简陋             ✅ 上传/下载进度

fetch 是浏览器原生 API,功能简单、偏底层。axios 在 fetch 之上提供了一套企业级请求管理能力,更适合正经项目。

4.3 第二层:具体接口方法(todos.js)

javascript 复制代码
// api/todos.js
import axios from './config';

export const getTodos = async () => {
    const res = await axios.get('/todos');
    return res.data;
}

每个接口方法只做一件事:发起请求、返回数据async/await 让异步代码读起来像同步,调用方不需要关心 Promise 链。

bash 复制代码
调用链:
  pages/todos.jsx
    → api/todos.js (getTodos)
      → api/config.js (axios 实例)
        → HTTP 请求

五、MockJS:让前端独立启动

5.1 核心思路

前端的两类路由,需要两种路由系统来分别处理:

bash 复制代码
前端应用:
├── 页面路由          → react-router-dom 处理
│   ├── /             → Home.jsx
│   └── /todos        → todos.jsx
│
└── 接口路由 (/api/*)  → vite-plugin-mock 处理
    └── /api/todos    → mock/todos.js

react-router-dom 只管页面跳转,管不了 HTTP 接口请求/api/todos 这个地址在浏览器看来就是一个 HTTP 请求 URL,Vite 开发服务器需要把它拦截下来,返回前端准备好的假数据。

5.2 vite.config.js:装上 Mock 插件

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',       // mock 文件目录
      localEnabled: true       // 开发环境启用
    })
  ],
})

viteMockServe 的工作流程:

markdown 复制代码
1. Vite 开发服务器启动
2. viteMockServe 读取 mock/ 目录下的所有文件
3. 拦截匹配到的 HTTP 请求路径
4. 直接返回 mock 文件中定义的 response
5. 请求根本不到后端 ------ 在 Vite 服务器层就被拦截了

5.3 定义 Mock 接口

yaml 复制代码
// mock/todos.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 string 匹配的请求路径,拦截 /api/todos
method string 匹配的 HTTP 方法 get / post / put / delete
timeout number 故意延迟 2 秒返回------模拟真实网络延迟,防止开发时太快、上线后才发现加载态 bug
response function 返回假数据。可以是一个对象,也可以是一个函数(动态生成)

timeout: 2000 是一个容易被忽略但很关键的细节。很多前端开发者在 mock 阶段看到数据秒出,就忽略了 loading 状态的处理。上线后网络延迟 500ms、1000ms,页面白屏、闪烁、无提示------这些都是 mock 阶段"太快"埋下的坑。故意加延迟让前端在本地就养成处理异步状态的肌肉记忆。

5.4 完整请求链路

bash 复制代码
页面访问 http://localhost:5173/todos
    │
    ├── react-router 匹配 → 渲染 <Todos /> 组件
    │
    └── <Todos /> useEffect 中调用 getTodos() 即 GET /api/todos
          │
          │  viteMockServe 拦截 ↓
          │
          │  url="/api/todos" method="get" 匹配成功!
          │  → 等待 2 秒(timeout)
          │  → 返回 { code: 0, todos: [...] }
          │
          ├── axios 收到响应
          ├── getTodos 返回 response.data
          ├── setTodos(data) ← 状态更新
          └── UI 重新渲染 ← 用户看到列表

六、页面层:连接接口与 UI

6.1 todos.jsx:状态驱动的页面

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 (
        <>
            Todos
        </>
    )
}

逐行解析:

useState([]) :todos 状态初始为空数组,等接口返回后通过 setTodos 更新。这里的初始值 [] 很重要------如果用 null,组件渲染时 todos.map() 会直接报错。

useEffect(() => {}, []) :空依赖数组,只在组件挂载时执行一次。这是数据获取的经典用法------进页面 → 调接口 → 拿数据 → 渲染。

IIFE(立即执行函数)

scss 复制代码
(async () => {
    const data = await getTodos();
    setTodos(data);
})();

为什么不能直接写 await

scss 复制代码
// ❌ 不合法:useEffect 的回调不能是 async 函数
useEffect(async () => {
    const data = await getTodos();  // async 返回 Promise
}, []);
// 原因:useEffect 的返回值必须是一个清理函数或 undefined。
// async 函数永远返回 Promise,React 会把这个 Promise 当成清理函数
// → 类型不匹配 → 潜在 bug

// ✅ 正确:普通函数 + 内部 IIFE
useEffect(() => {
    (async () => {
        const data = await getTodos();
        setTodos(data);
    })();
}, []);

七、baseURL 一键切换:从 Mock 到真实后端

这是前端接口工程最精彩的设计:

php 复制代码
// api/config.js
const instance = axios.create({
    baseURL: '/api',                  // ← 开发环境:走 mock
    // baseURL: 'http://localhost:3000',  // ← 生产联调:切到真实后端
    timeout: 5000,
})

切换对比

bash 复制代码
开发阶段 (Mock):
  getTodos() → GET /api/todos
  → Vite 代理拦截 → mock/todos.js → 返回假数据

联调阶段 (真实后端):
  注释掉 baseURL: '/api'
  启用 baseURL: 'http://localhost:3000'
  getTodos() → GET http://localhost:3000/todos
  → 真实 Node + Koa 后端 → 返回数据库数据

改一行代码,整个应用的所有接口全部切换------这就是 baseURL 集中管理的威力。

为什么用 /api 前缀?

bash 复制代码
前端开发服务器:localhost:5173
真实后端服务器:localhost:3000

/api/todos  → Vite Mock 拦截(开发期)
/api/todos  → Vite proxy 转发到 localhost:3000/todos(联调期)

/api 前缀是一个约定 ------所有接口 URL 都以 /api 开头,浏览器看到 localhost:5173/api/todos,Vite 开发服务器可以决定是自己处理(mock)还是转发(proxy)。如果没有这个前缀,/todos 到底是页面路由还是接口请求就分不清了:

bash 复制代码
/todos      → 可能是页面路由(react-router)也可能是接口(http请求)
/api/todos  → 明确是接口请求

八、完整架构图

bash 复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        前端应用 (localhost:5173)                   │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐    │
│  │                     UI 层                                  │    │
│  │  App.jsx ──Router── Nav.jsx                               │    │
│  │              ├── /      → Home.jsx                        │    │
│  │              └── /todos → todos.jsx [useState + useEffect]│    │
│  └──────────────────────┬───────────────────────────────────┘    │
│                         │ import { getTodos }                     │
│                         ▼                                         │
│  ┌──────────────────────────────────────────────────────────┐    │
│  │                   接口工程层 api/                           │    │
│  │  config.js ← axios.create({baseURL:'/api', timeout:5000}) │    │
│  │  todos.js  ← getTodos() → axios.get('/todos')             │    │
│  └──────────────────────┬───────────────────────────────────┘    │
│                         │ HTTP GET /api/todos                     │
│                         ▼                                         │
│  ┌──────────────────────────────────────────────────────────┐    │
│  │                   Vite 开发服务器                           │    │
│  │  viteMockServe({ mockPath: 'mock', localEnabled: true })   │    │
│  │       │                                                     │    │
│  │       ├── 开发期: 拦截 /api/todos → mock/todos.js           │    │
│  │       └── 联调期: proxy 到 localhost:3000                   │    │
│  └──────────────────────────────────────────────────────────┘    │
│                                                                   │
│  ┌──────────────────────┐                                        │
│  │  mock/todos.js        │     ← 开发期数据源                      │
│  │  { url:'/api/todos',  │                                        │
│  │    method:'get',      │                                        │
│  │    response: {...} }  │                                        │
│  └──────────────────────┘                                        │
└─────────────────────────────────────────────────────────────────┘

                         ↓ 后端完成后,改一行 baseURL

┌─────────────────────────────────────────────────────────────────┐
│                    后端服务 (localhost:3000)                      │
│  Node + Koa + MySQL → /todos → JSON                             │
└─────────────────────────────────────────────────────────────────┘

九、总结

本文通过一个全栈 Todos 项目,梳理了前端独立开发的完整工程链路:

  1. 三驾马车:React(组件)+ React Router(路由)+ useState/useEffect(状态管理)构成前端骨架
  2. 前端接口工程api/ 目录统一管理所有接口------config.js 管配置(axios 实例 + baseURL + timeout),todos.js 管具体方法(一个模块一个 JS 文件)
  3. MockJS 解耦vite-plugin-mock 在 Vite 服务器层拦截 /api/todos 请求,返回 mock/ 目录定义的假数据,前端无需等后端
  4. timeout 细节:mock 接口故意加 2 秒延迟,模拟真实网络环境,倒逼前端写好 loading 状态处理
  5. baseURL 一键切换 :注释 /api,启用 http://localhost:3000,整个应用所有接口从 mock 切到真实后端------只需改一行
  6. IIFE + useEffectuseEffect 不支持直接 async,用 IIFE 包裹 await 是最佳实践

核心思想一句话:前端不止是画页面,前端也是一门独立的工程学科------有自己的接口层、数据层和独立的开发闭环。 这才是前后端分离的本质意义。

相关推荐
用户938515635075 小时前
从零在浏览器里跑 DeepSeek-R1:WebGPU + Transformers.js 全链路实战(三)
前端·react.js·typescript
excel5 小时前
当前端行情变差,我们为什么还要坚持?
前端
码事漫谈6 小时前
世界由什么构成——这个问题比你想的更缠人
后端
鸿是江边鸟,曾是心上人6 小时前
快速搭建HTTPS本地开发环境
前端
摇滚侠6 小时前
《SpringBoot 3:入门与应用实战》第 2 章 IOC 思想与实现 阅读笔记 1
spring boot·笔记·后端
山荷枝6 小时前
03-框架--Spring
java·后端·spring
To_OC7 小时前
写了 5 个表单 Demo 后,我终于彻底搞懂了 React 受控与非受控组件
前端·react.js·前端框架
Sterting7 小时前
第9课 Vue Router 路由
前端·vue.js
ChaHae-In7 小时前
SpringBoot统一功能处理
java·spring boot·后端