React Todos 前端独立开发全解:用 vite-plugin-mock + axios 封装,再也不等后端接口

React Todos 前端独立开发全解:用 vite-plugin-mock + axios 封装,再也不等后端接口

适用技术栈:React 19 + Vite 8 + axios + vite-plugin-mock 3。目标读者:正在学前后端分离、但总被后端进度卡住的前端新手。读完你会得到一个"前端先行"的最小可复用模板:路由懒加载、API 目录工程化、Mock 假数据、切换真后端只改一行配置。代码运行未验证。


文章目录

  • [React Todos 前端独立开发全解:用 vite-plugin-mock + axios 封装,再也不等后端接口](#React Todos 前端独立开发全解:用 vite-plugin-mock + axios 封装,再也不等后端接口)
    • [前言:写一个 Todo 应用,为什么非要等后端接口?](#前言:写一个 Todo 应用,为什么非要等后端接口?)
    • [1. 项目结构与技术栈:前端三驾马车 + 后端骨架](#1. 项目结构与技术栈:前端三驾马车 + 后端骨架)
      • [1.1 目录设计](#1.1 目录设计)
      • [1.2 技术栈概览](#1.2 技术栈概览)
    • [2. 前端路由与代码分割:BrowserRouter + lazy + Suspense 三件套](#2. 前端路由与代码分割:BrowserRouter + lazy + Suspense 三件套)
      • [2.1 为什么要做路由懒加载?](#2.1 为什么要做路由懒加载?)
      • [2.2 最小可复用模板(本项目 App.jsx)](#2.2 最小可复用模板(本项目 App.jsx))
      • [2.3 `<Link>` vs 原生 `<a>`:为什么 SPA 推荐 Link](#2.3 <Link> vs 原生 <a>:为什么 SPA 推荐 Link)
    • [3. src/api 工程化:axios 实例 + 按模块拆分](#3. src/api 工程化:axios 实例 + 按模块拆分)
      • [3.1 axios.create:统一 baseURL + timeout 的全局层](#3.1 axios.create:统一 baseURL + timeout 的全局层)
      • [3.2 按模块拆分接口:一个文件管一种资源](#3.2 按模块拆分接口:一个文件管一种资源)
    • [4. 页面拿数据:useEffect + 立即执行异步函数(IIFE)](#4. 页面拿数据:useEffect + 立即执行异步函数(IIFE))
    • [5. vite-plugin-mock:前端自己给自己"造后端"](#5. vite-plugin-mock:前端自己给自己"造后端")
      • [5.1 插件注册(vite.config.js)](#5.1 插件注册(vite.config.js))
      • [5.2 编写接口规则(mock/todos.js)](#5.2 编写接口规则(mock/todos.js))
    • [6. 后端写完了怎么切?两种最小改动方案](#6. 后端写完了怎么切?两种最小改动方案)
      • [方案 A(推荐):Vite 代理 + 路径重写](#方案 A(推荐):Vite 代理 + 路径重写)
      • [方案 B:直接改 axios baseURL](#方案 B:直接改 axios baseURL)
    • [7. 常见错误排错自检表(收藏用)](#7. 常见错误排错自检表(收藏用))
    • [8. 五步自检:你的"前端先行"项目合格了吗?](#8. 五步自检:你的"前端先行"项目合格了吗?)
    • [9. 延伸练习:从"能跑"到"工程化"](#9. 延伸练习:从"能跑"到"工程化")
    • 结语:真正的前后端分离,从"前端能独立交付"开始

前言:写一个 Todo 应用,为什么非要等后端接口?

你可能刚学会 React + axios,想写一个 TodoList。按照"正确流程":先让后端写 GET /todosPOST /todos,你再拿数据渲染。结果后端同学忙着别的需求,接口一周都没影------你卡在"页面能写,但没数据就是空的"这个状态上,寸步难行。

这个项目用一个 Todos 小例子完整演示了一套"前端先行"的开发模式:后端只要把接口契约 (URL / method / 返回结构)定下来,前端自己用 vite-plugin-mock 提供一模一样的假 JSON,页面、状态、交互全部写完;后端真正写完接口后,只改一行配置(Vite proxy 或 axios baseURL),页面代码和 API 封装代码零改动即可联调。

本文包含:React Router 懒加载配置、src/api 目录的 axios 工程化封装、useEffect 正确的异步写法(为什么不能 async)、Mock 规则的写法、联调切换方案,以及一份"常见报错→原因→排查"的自检清单。全部内容可回溯到源码证据。

1. 项目结构与技术栈:前端三驾马车 + 后端骨架

1.1 目录设计

复制代码
todos-fullstack/
├── readme.md                         # 顶层设计说明(解耦思路、API 目录职责)
├── fronted/todos/                    # 前端项目根
│   ├── vite.config.js                # Vite + vite-plugin-mock 注册
│   ├── mock/
│   │   └── todos.js                  # GET /api/todos Mock 规则 + 2 秒假延迟
│   └── src/
│       ├── main.jsx                  # React 19 createRoot 挂载
│       ├── App.jsx                   # BrowserRouter + lazy + Suspense
│       ├── components/Nav.jsx        # Link 导航(不是原生 <a>)
│       ├── pages/
│       │   ├── Home.jsx              # / 首页
│       │   └── Todos.jsx             # /todos 页面(useEffect + IIFE 调接口)
│       └── api/
│           ├── config.js             # axios.create({ baseURL:'/api', timeout:5000 })
│           └── todos.js              # 命名导出 getTodos(按模块拆分)
└── backend/
    └── package.json                  # 目前只装 koa 3,业务代码待写(前端先行状态)

1.2 技术栈概览

前端(已落地):React 19 / react-router-dom 7 / Vite 8 / axios 1 / vite-plugin-mock 3 / zustand 5(已安装,当前页面未实际使用)。

后端(骨架状态):koa 3(package.json 有依赖,但无 index.js、无 mysql 驱动,readme 规划但未落地)。

⚠️ 事实披露:zustand 仅在依赖中声明,src 中没有任何 useStore 代码;后端没有写业务逻辑,本项目当前是纯前端 + Mock 自给自足。不能声称"项目已使用 zustand"或"有完整 Koa 后端"。

2. 前端路由与代码分割:BrowserRouter + lazy + Suspense 三件套

2.1 为什么要做路由懒加载?

如果在 App.jsx 顶部写:

jsx 复制代码
import Home from './pages/Home'
import Todos from './pages/Todos'

首屏一次性把两个页面的 JS 全下载了。页面多了以后首屏极慢。

React 提供 lazy 做路由级代码分割------只有 URL 匹配到了对应页面,浏览器才去下载那个页面的 chunk。Suspense fallback=... 就是下载过程中的兜底显示,没有它会白屏

2.2 最小可复用模板(本项目 App.jsx)

jsx 复制代码
import React, { lazy, Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Nav from './components/Nav'

const Home = lazy(() => import('./pages/Home'))
const Todos = lazy(() => import('./pages/Todos'))

export default function App() {
  return (
    <Router>
      <Nav />
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/todos" element={<Todos />} />
        </Routes>
      </Suspense>
    </Router>
  )
}
jsx 复制代码
<Link to="/todos">Todos</Link>

Link 底层渲染成 <a>,但拦截了 e.preventDefault(),改用 HTML5 History API 改 URL,然后 React Router 内部匹配新路径,局部替换组件,不整页刷新 ------没有白屏、没有 CSS/JS 重新下载,用户体验顺滑。而原生 <a href="/todos"> 会重新请求 index.html + 全部资源,有明显闪烁。

3. src/api 工程化:axios 实例 + 按模块拆分

这是将来联调零改动页面代码的关键。

3.1 axios.create:统一 baseURL + timeout 的全局层

src/api/config.js

js 复制代码
import axios from 'axios'

const instance = axios.create({
  baseURL: '/api',      // 所有请求自动拼 /api 前缀
  timeout: 5000,        // 5 秒无响应自动失败,防止转圈卡死
})

export default instance

为什么不用 fetch?材料注释里写得很直接:"fetch 的缺点是功能小"。展开四点:

  1. fetch 遇到 HTTP 4xx/5xx 不抛异常 ,返回的仍是 resolved Promise,要手动判断 res.ok;axios 自动 reject。
  2. fetch 没有原生 timeout,要 AbortController + setTimeout 自己凑;axios 一个字段搞定。
  3. fetch 无拦截器;axios 有 request/response 拦截器,将来统一加 token、统一错误 Toast,全项目加在一处就行。
  4. fetch 无"独立实例"概念;axios.create 可以建多个实例(例如一个请求 /api,一个请求 /third),互不干扰。

3.2 按模块拆分接口:一个文件管一种资源

src/api/todos.js

js 复制代码
import instance from './config'

// 按模块命名导出:一个资源(todos)一个文件
export const getTodos = async () => {
  const res = await instance.get('/todos')  // 拼 baseURL 后实际 GET /api/todos
  return res.data                           // 返回值是后端响应体,不是整个 res
}

这样做的好处:接口 URL 改了、请求库换了(axios → ky)、要加 TS 类型......都只改 API 目录,页面代码完全不动。多人协作时,A 写 todos 接口,B 写 users 接口,互不冲突。

4. 页面拿数据:useEffect + 立即执行异步函数(IIFE)

src/pages/Todos.jsx 的数据请求:

jsx 复制代码
import { getTodos } from '../api/todos'
import { useEffect, useState } from 'react'

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

  useEffect(() => {
    // 为什么不能直接 useEffect(async () => ...)?
    // 答:async 函数返回 Promise,React 期望 useEffect 返回清理函数或 undefined
    (async () => {
      const data = await getTodos()
      setTodos(data.todos)  // 响应体结构 {code:0, todos:[...]},取 data.todos 数组
    })()
  }, [])  // 空依赖:首次渲染只执行一次

  return <div><h1>Todos</h1></div>
}

这个"IIFE 模式"可以直接背下来复用:当 useEffect 里要 await,但外层函数不能 async 时就包一层。

⚠️ 易错点:Mock 返回 {code:0, todos:[...]},必须取 data.todos。如果直接 setTodos(data),把对象当数组 map,UI 不会显示但也不报错,排查极其痛苦。

5. vite-plugin-mock:前端自己给自己"造后端"

5.1 插件注册(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 规则文件放在项目根的 mock/ 目录
      localEnabled: true,  // 只在开发环境启用,生产构建会自动剔除
    })
  ],
})

5.2 编写接口规则(mock/todos.js)

js 复制代码
export default [
  {
    url: '/api/todos',        // 必须和 axios 实际请求的 URL 完全一致(baseURL 拼好后)
    method: 'get',            // GET/POST 对应 axios.get/post
    timeout: 2000,            // 模拟 2 秒网络延迟,体验更真实,顺便测 Loading
    response: (req, res) => ({
      code: 0,
      todos: [
        { id: 1, title: '学习react', completed: false },
        { id: 2, title: '学习vue',   completed: false },
      ]
    })
  }
]

效果:前端发起 GET http://localhost:5173/api/todos,Vite 开发服务器在 HTTP 层就拦截住,不经过真实网络,timeout 后返回假 JSON。你的 axios、页面、状态管理代码完全按"真后端"写,但不需要真后端。

⚠️ 易错点:这里的 url 必须和 axios 实际请求一致。axios baseURL 是 /api,getTodos 调 /todos,加起来就是 /api/todos------所以 mock 规则必须写 /api/todos ,只写 /todos 会命中不了,报 404。

6. 后端写完了怎么切?两种最小改动方案

这就是前面"API 工程化"的回报:后端 Koa 写完跑在 http://localhost:3000/todos 时,页面和 src/api/todos.js 一行代码都不用改。

方案 A(推荐):Vite 代理 + 路径重写

vite.config.jsserver.proxy,把 /api/* 转发到 3000 端口,并去掉 /api 前缀(后端路径是 /todos 不是 /api/todos):

js 复制代码
export default defineConfig({
  plugins: [react(), viteMockServe({...})], // 联调时可把 localEnabled 改为 false 关掉 Mock
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')  // /api/todos → /todos
      }
    }
  }
})

优点:前端仍然请求同域 /api/todos没有跨域问题,不需要后端开 CORS。线上部署时同理(Nginx 反代)。

方案 B:直接改 axios baseURL

js 复制代码
// api/config.js
const instance = axios.create({
  baseURL: 'http://localhost:3000',  // 直接请求后端
  timeout: 5000,
})

缺点:浏览器跨域(5173 → 3000),需要后端开启 CORS(koa 要装 @koa/cors);同时后端接口路径要和前端一致(/todos 而不是 /api/todos,不然再加 rewrite 逻辑)。一般只在独立环境开发时用。

7. 常见错误排错自检表(收藏用)

现象 可能原因 排查 / 处理
路由懒加载后白屏,控制台报 "suspended" lazy 组件缺少 <Suspense> 兜底 <Routes> 外层包 <Suspense fallback='Loading...'>
/todos 页面一直空,Network 200 但数据不对 Mock 返回 {code,todos} 但页面 setTodos(data) 没取 .todos setTodos(data.todos);Network 面板检查响应体字段
请求 /api/todos 404 Mock 规则写的 url 是 /todos(少了 /api 前缀) mock/todos.js 的 url 改成 /api/todos
请求 5 秒自动失败 / 转圈卡住 axios timeout:5000 触发;或 Mock timeout 大于超时 Mock timeout 调短(如 2s),或真后端排查接口响应
useEffect 控制台警告,或异步结果不可控 useEffect(async () => { await ... }) 直接写了 async 改为 IIFE:(async () => await getTodos())() 包裹
点导航后整页刷新、闪烁 组件里用了原生 <a href> 没加 Link 改为 <Link to="...">
切到真后端后接口 3000 端口报 CORS 错误 用了方案 B 直接跨域请求 改方案 A(Vite proxy)或后端装 @koa/cors

8. 五步自检:你的"前端先行"项目合格了吗?

照着这个清单走一遍,能立刻找出漏网之坑:

  • 路由lazy 组件外层有没有 <Suspense fallback>?点导航是不是不刷新整页?
  • API 层 :axios 是不是用 axios.create 实例?baseURL 有没有统一?接口函数是不是按模块拆分 + 命名导出?
  • 数据流:useEffect 里是不是用 IIFE 包了 await?接口 404 有没有 catch 容错提示?
  • Mock :mock 规则 url 是不是和 baseURL 拼好的实际请求一致?response 结构是不是和 setTodos(data.todos) 字段对齐?
  • 切换预案:后端写完后是走 Vite proxy 还是改 baseURL?至少想好一种路线并能解释为什么(推荐方案 A 无 CORS)。

9. 延伸练习:从"能跑"到"工程化"

当你把模板跑通后,可按这个路线继续加固(和面试知识点高度重合):

  1. 给 axios 实例加 request 拦截器注入 token,response 拦截器统一弹 Toast 错误。
  2. 真正把 zustand 用起来:把 todos 数组迁出页面 useState,跨页面共享(比如 Home 显示总数)。
  3. 在 backend 目录写最小 Koa GET /todos,返回和 Mock 相同的 JSON;然后用方案 A 切换并验收。
  4. 给 Todos 页面加 POST /api/todos(输入框 + 按钮),同步加 Mock、加 API 模块、加 Koa 路由,体验"完整 CRUD 的契约驱动开发"。
  5. 把整个项目从 JSX 改成 TSX,给 axios 响应写泛型类型,体验"接口契约 → TS 类型 → 组件里类型提示"的链路。

结语:真正的前后端分离,从"前端能独立交付"开始

很多人以为前后端分离 = 前端写 React,后端写 Koa,拼在一起就完事。其实真正的分离是开发节奏能解耦:前端不依赖后端进度,能用 Mock 独立把界面、交互、校验跑通;后端把接口契约按时交付,联调只是"切换流量去向"的配置操作。

这个 Todos 项目虽然小,但演示了完整套路:lazy + Suspense(首屏性能) + src/api 工程化(职责分离) + vite-plugin-mock(节奏解耦) + Vite proxy(联调无缝切换)。把这个四件套变成你的默认项目模板,下次写任何 CRUD 项目都不会再被后端进度卡死,也不会被"接口改一处我搜全项目"折磨。

下一步,就从上面的"五步自检"和"延伸练习"开始,照着模板写一个属于你自己的版本吧。


标签(3-5):React、Vite、axios、前后端分离、vite-plugin-mock

相关推荐
cyadyx1 小时前
【vue】Pinia相对Vuex
前端·javascript·vue.js
码上成长1 小时前
小程序请求层怎么封装?
前端·小程序
IT_陈寒2 小时前
Redis的KEYS命令差点把我的生产环境拖垮,改用SCAN吧
前端·人工智能·后端
deli0070072 小时前
打飞碟射击场:霓虹星空下飞碟满天飞,鼠标点射一枪一个
前端·ai编程
zoe驿鹿2 小时前
【CI/CD】前端工程化与自动化:从“手工作坊”到“现代化工厂”的演进之路
前端·ci/cd·自动化
lupai2 小时前
增值税发票 OCR 识别 API 新手接入指南
java·前端·ocr
IMPYLH2 小时前
HTML 的 <figure> 元素
前端·javascript·html
reasonsummer2 小时前
【办公类-120-01】20260812闵行区幼儿园概况表的数据统计(户籍、五类性质、优抚子女)
前端·python