告别等后端!React+Vite 前端独立开发全流程:接口封装 + Mock 方案 + 状态管理

在前后端分离的开发模式下,前端开发者常常面临一个尴尬的处境:页面逻辑已经梳理清楚,但后端接口还没开发完成,导致前端进度被卡住。要么对着空页面写死数据,等接口好了再挨个替换,既低效又容易出问题。

本文就以一个经典的 Todos 全栈项目为例,完整讲解前端如何实现接口工程化封装,借助 Mock 方案实现完全独立于后端的前端开发,同时梳理清楚路由选型、全局状态管理等核心知识点,打造一套规范、可维护的前端项目架构。

一、项目整体架构与前后端职责分离

1.1 技术栈总览

我们采用典型的前后端分离架构,两端职责完全解耦:

  • 前端技术栈:React 18 + React Router v6 + Zustand + Axios + Vite
  • 后端技术栈:Node.js + Koa + MySQL

前端负责页面渲染、交互逻辑、路由控制、客户端状态管理、接口调用;后端负责数据持久化、核心业务逻辑、权限控制、接口服务。两者通过 HTTP 接口对接,开发进度互不阻塞。

1.2 前端独立开发的核心思想

前端工程化的一个重要目标,就是不依赖后端也能完成完整的业务开发。我们通过 Mock 技术模拟后端接口返回数据,先把前端的页面流程、交互逻辑、状态管理全部跑通;等后端接口开发完成后,只需要修改一处配置,就能无缝切换到真实接口。

1.3 Mock 与 BFF 的区别

很多初学者容易混淆这两个概念,这里做一个清晰的区分:

  • Mock:在前端开发环境拦截请求,直接返回预设的假数据,完全不经过真实后端服务,仅用于本地开发阶段。
  • BFF(Backend For Frontend) :专门服务于前端的后端层,接收前端请求后,可以返回假数据,也可以转发给真实后端,还可以做数据聚合、接口适配,一般用于生产环境或多后端场景。

二、前端路由选型:HashRouter vs BrowserRouter

路由是单页应用(SPA)的核心,react-router-dom 提供了两种最常用的路由模式,适用场景完全不同。

2.1 BrowserRouter(History 模式)

基于 HTML5 标准的 History API(pushState / replaceState)实现,URL 地址干净整洁,没有 # 号。

  • 优点:URL 美观,符合常规网页地址习惯,是正式项目的标准选型。
  • 缺点 :生产环境需要后端配合配置重定向(比如 Nginx 的 try_files),否则刷新页面会出现 404。
  • 适用场景:正式线上项目、有后端配合的生产环境。

2.2 HashRouter(Hash 模式)

利用 URL 中 # 后面的哈希片段做路由匹配,哈希变化不会触发页面刷新,也不会向服务器发送请求。

  • 优点:不需要后端任何配置,刷新页面不会 404,部署成本极低。
  • 缺点 :地址带 # 号,不够美观。
  • 适用场景:静态页面部署、Demo 原型、无后端配合的快速开发。

2.3 基础使用示例

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

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

注意:React Router v6 用 <Routes> 替代了旧版的 <Switch>,匹配规则也更精准。

三、前端接口工程化:Axios 封装与分层管理

很多初学者喜欢把 axios 请求直接写在组件的 useEffect 里,短期看没问题,项目一旦变大,维护成本会急剧上升。规范的项目一定会做接口分层。

3.1 为什么要做接口分层?

  1. 代码复用:多个组件调用同一个接口时,不需要重复编写请求逻辑,直接导入函数即可。
  2. 职责解耦:组件只关心业务数据和渲染,不用感知接口地址、请求头、超时这些细节。
  3. 易于维护:后端接口地址、参数发生变更时,只需要修改 api 层一处代码,所有调用的组件自动生效。
  4. 统一处理:方便后续添加请求拦截(统一加 Token)、响应拦截(统一处理错误码)。

3.2 第一步:封装 Axios 实例

我们不直接使用原生 axios,而是创建一个自定义实例,统一配置全局参数。

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

// 创建统一的 axios 实例
const request = axios.create({
  baseURL: '/api',  // 所有接口的统一前缀
  timeout: 5000     // 请求超时时间,单位毫秒
});

export default request;

baseURL 会自动和后续请求的相对路径拼接,比如 request.get('/todos') 最终请求地址就是 /api/todos

3.3 第二步:按业务模块封装接口

把同一个业务模块的接口放在同一个文件里,比如所有 todo 相关的接口都放在 api/todos.js

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

/**
 * 获取 Todo 列表
 */
export const getTodos = async () => {
  const res = await request.get('/todos');
  // res.data 是后端返回的真正业务数据
  return res.data;
};

组件里只需要导入 getTodos 函数调用即可,完全不用关心内部是用 axios 还是 fetch。

四、Mock 方案:前端完全独立开发的核心

我们使用 vite-plugin-mock 来实现接口模拟,它工作在 Vite 开发服务器层面,比传统的 Mock.js 调试体验更好。

4.1 为什么选 vite-plugin-mock

对比前端常用的 Mock.js:

  • Mock.js:在浏览器端拦截 XHR 请求,浏览器 Network 面板看不到真实的请求,调试不直观。
  • vite-plugin-mock:在 Vite 开发服务器(Node 层)拦截请求,Network 面板能看到完整的请求和响应,和调用真实接口的体验完全一致。

4.2 完整配置步骤

1. 安装依赖

-D 表示开发依赖,只在本地开发环境生效,打包不会进入生产代码。

css 复制代码
npm i -D vite-plugin-mock

2. Vite 配置文件注册插件

修改项目根目录的 vite.config.js,把 mock 插件加入 plugins 数组。

javascript 复制代码
// vite.config.js
import { defineConfig } from 'vite';
import { vitePluginMock } from 'vite-plugin-mock';

export default defineConfig({
  plugins: [
    vitePluginMock({
      mockPath: './mock',  // mock 接口文件的存放目录
      enable: true         // 总开关:true 开启 mock,false 关闭
    })
  ]
});

⚠️ 重要提示:修改 vite.config.js 后,必须重启开发服务器,配置才会生效。

3. 编写 Mock 接口文件

在项目根目录新建 mock 文件夹,按业务模块创建对应的 mock 文件,比如 mock/todos.js

插件约定:每个 mock 文件默认导出一个数组,数组中的每个对象对应一条接口规则。

javascript 复制代码
// mock/todos.js
export default [
  {
    url: '/api/todos',       // 接口地址,要和 axios 请求的完整路径一致
    method: 'get',           // 请求方法
    timeout: 200,            // 模拟网络延迟,单位毫秒
    response: () => {
      // 返回模拟的响应数据
      return {
        code: 0,             // 业务状态码,0 表示成功
        message: 'success',
        todos: [
          { id: 1, title: '学习前端接口工程', completed: true },
          { id: 2, title: '学习后端开发', completed: false }
        ]
      };
    }
  }
];

4.3 效果与切换

配置完成后,前端发起 get /api/todos 请求时,Vite 开发服务器会直接拦截请求,返回我们写好的模拟数据,完全不需要启动后端服务。

当后端接口开发完成后,只需要把 vite.config.js 中的 enable 改为 false,请求就会走真实的后端接口,业务代码一行都不用改,实现平滑切换。

五、全局状态管理:Context vs Zustand

跨组件共享状态是 React 开发的高频需求,这里对比两种主流方案,帮你理清选型思路。

5.1 Context 方案

Context 是 React 原生提供的状态共享方案,原理是把数据挂在组件树的 Provider 节点上,后代组件通过 useContext 读取。

  • 缺点:需要层层包裹 Provider,组件树嵌套深;只要 Provider 中的数据变化,所有消费组件都会重新渲染,性能开销大;复杂状态需要搭配 useReducer,代码臃肿。
  • 适用场景:组件树局部的轻量 UI 状态传递,比如主题、语言配置。

5.2 Zustand 方案

Zustand 是目前非常流行的轻量状态管理库,它的 store 是独立于组件树的全局对象,组件直接 import 导入使用。

  • 优点:无需 Provider 包裹,侵入性极低;组件按需订阅状态,只有自己关心的数据变化才会触发重渲染,性能更好;API 简洁,代码量少,天然支持异步和中间件。
  • 适用场景:跨组件的全局业务状态共享。

5.3 一句话总结

Zustand 可以完全替代「Context + useReducer」的全局状态方案;如果只是组件树局部的轻量状态传递,Context 依然适用,两者并不冲突。

六、完整页面实战:Todo 列表页

现在我们把前面所有知识点串起来,实现一个完整的 Todo 列表页面。

6.1 导航组件

使用 <Link> 替代原生 <a> 标签,实现前端无刷新路由跳转。

javascript 复制代码
// src/components/Nav.jsx
import { Link } from 'react-router-dom';

const Nav = () => {
  return (
    <nav style={{ padding: '10px 20px', borderBottom: '1px solid #ccc' }}>
      <Link to="/" style={{ marginRight: 20 }}>首页</Link>
      <Link to="/todos">Todo 列表</Link>
    </nav>
  );
};

export default Nav;

6.2 Todo 页面组件

useEffect 中调用接口,把返回的数据存入 state 并渲染到页面。

javascript 复制代码
// src/pages/Todos.jsx
import { useEffect, useState } from 'react';
import { getTodos } from '../api/todos';

function Todos() {
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    // useEffect 回调不能直接写 async,用立即执行函数包裹
    (async () => {
      const result = await getTodos();
      // mock 返回格式为 {code:0, todos:[]},取 todos 数组
      setTodos(result.todos);
    })();
  }, []);

  return (
    <div style={{ padding: 20 }}>
      <h2>Todo 列表</h2>
      <ul>
        {todos.map(item => (
          <li 
            key={item.id} 
            style={{ 
              listStyle: 'none',
              padding: '8px 0',
              textDecoration: item.completed ? 'line-through' : 'none',
              color: item.completed ? '#999' : '#333'
            }}
          >
            {item.title}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Todos;

为什么用立即执行函数? useEffect 的回调函数如果直接声明为 async,会返回一个 Promise;而 useEffect 期望的返回值是清理函数。因此用 IIFE(立即调用函数表达式)包裹异步逻辑是标准写法。

6.3 根组件路由配置

最后在 App.jsx 中整合导航和路由:

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

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

export default App;

七、项目架构总结

这套架构是中大型 React 项目的标准范式,核心优势非常明显:

  1. 职责清晰:组件层负责渲染交互,api 层负责网络请求,mock 层负责模拟数据,分层明确,各司其职。
  2. 独立开发:前端完全不需要等后端接口,基于 Mock 就可以跑通完整的业务流程。
  3. 易于维护:接口、配置、状态都有统一的管理入口,需求变更时修改成本极低。
  4. 平滑切换:从 Mock 环境切换到真实后端,只需要改一个配置项,业务代码零改动。

后续还可以在此基础上扩展:添加请求拦截器统一注入 Token、响应拦截器统一处理错误提示、用 Zustand 接管全局 Todo 状态、增加新增 / 删除 / 修改的 mock 接口等,逐步完善成一个完整的前端工程化项目。

相关推荐
PedroQue991 小时前
@meng-xi/vite-plugin v1.3.0:generateUni 一键流水线
前端·vite
胡萝卜术1 小时前
权限系统的四道防线:从数据库事务锁到操作级保护规则的完整设计
前端·javascript·面试
IMPYLH1 小时前
HTML 的 <legend> 元素
java·前端·html
werdedeage1 小时前
用 useReducer 管理多参考图生成器的前端状态
react.js·typescript
真夜1 小时前
RN热更新安全问题
前端·react native
Zeroplucky1 小时前
Binder 线程池耗尽案例:持锁同步调用 VHAL 导致 SystemUI 卡顿或 ANR
前端
Zeroplucky1 小时前
从 registerContentObserver 剖析 Android Binder 流程
前端
Yinlin1241 小时前
深入悬浮组件实现
前端
yangzheui1 小时前
nvue页面事件穿透到下层元素解决办法
前端