在现代前端开发中,前后端分离已成为主流模式。这种模式下,前端工程师不再依赖后端接口的实时可用性,而是可以通过工具模拟数据接口,从而加速开发流程。前端开发者会经常遇到项目启动阶段后端接口尚未就绪的情况。这时,使用 Mock.js 来伪造 API 响应就显得尤为实用。它不仅能生成随机数据,还支持分页、查询参数解析等功能,让前端代码在开发环境中运行顺畅,上线时只需切换 baseURL 即可无缝过渡。
本文将基于一个简单的帖子列表(Post List)场景,详细讲解如何在 Vite + React 项目中集成 Mock.js。我们会从接口文档阅读开始,逐步实现数据模拟、axios 配置、API 调用、Zustand 状态管理,以及 React 组件渲染。
前后端分离下的数据挑战
首先,我们来谈谈数据来源。在一个典型的帖子列表功能中,真实数据存储在后端数据库,前端通过 HTTP 请求(如 axios)调用 API 获取。例如,接口文档定义了一个 GET 请求:/api/posts?page=1&limit=10,返回格式为:
JSON
{
"status": 200,
"list": [ /* Post 数组 */ ]
}
这里的 Post 对象可能包含标题、简介、发布时间、用户信息、标签、缩略图等字段。如果后端接口还未开发完成,前端是否需要等待?答案是否定的。在前后端分离的基础上,前端可以自行 mock 数据。通过伪造请求响应,前端开发者可以独立推进 UI 开发和逻辑测试,一旦后端接口上线,只需修改 axios 的 baseURL,就能实现无缝对接。
这种方法的核心优势在于:
- 并行开发:前后端团队同时开工,提高效率。
- 测试友好:模拟各种场景,如分页、空数据、错误响应。
- 减少依赖:避免因后端延误导致前端停滞。
为了实现 mock,我们选择 Mock.js 这个轻量级库。它支持随机数据生成、模板语法,并能拦截 axios 请求。在 Vite 项目中,Mock.js 的集成非常简单,通常结合 vite-plugin-mock 等插件启动 mock 服务。
Mock.js 的基础使用
Mock.js 的语法简洁强大。我们先来看如何在项目中设置 mock 文件。以帖子列表为例,创建一个 mock/posts.js 文件,用于定义 API 路由和响应逻辑。
Mock.js 提供了 @ 占位符来生成随机数据,例如:
- @ctitle(8, 20):生成 8-20 个中文字符的标题。
- @integer(1, 30):生成 1-30 的整数。
- @datetime("yyyy-MM-dd HH:mm"):生成指定格式的日期时间。
- @image(300x200):生成 300x200 的随机图片 URL。
- @cname(2, 4):生成 2-4 个中文字符的姓名。
此外,Mock.js 支持数组生成,如 'list|45' 表示生成 45 条数据的数组。我们定义一个 tags 数组(如 ["前端", "后端", "职场", "AI", "副业", "面经", "算法"]),然后使用 Mock.Random.pick(tags, 2) 随机挑选 2 个标签。
以下是完整的 mock/posts.js 示例代码:
JavaScript
import Mock from 'mockjs'
const tags = ["前端", "后端", "职场", "AI", "副业", "面经", "算法"]
const posts = Mock.mock({
'list|45': [ // list 45 条
{
title: '@ctitle(8, 20)',
brief: '@ctitle(20, 100)',
totalComments: '@integer(1, 30)',
totalLinks: '@integer(0, 500)',
publishedAt: '@datetime("yyyy-MM-dd HH:mm")',
user: {
id: '@integer(1, 100)',
name: '@cname(2, 4)',
avatar: '@image(300x200)',
},
tags: () => Mock.Random.pick(tags, 2),
thumbnail: '@image(300x200)',
pics: [
'@image(300x200)',
'@image(300x200)',
'@image(300x200)',
],
id: '@increment(1)'
}
]
}).list
export default [
{
url: '/api/posts',
methods: 'GET',
response:({ query }, res) => {
// console.log(query, ' ????');
const { page = '1', limit = '10' } = query;
const currentPage = parseInt(page, 10);
const size = parseInt(limit, 10);
if (isNaN(currentPage) || isNaN(size) || currentPage < 1 || size < 1) {
return {
code: 400,
msg: 'Invalid page or pageSize',
data: null,
}
}
const total = posts.length; // count
const start = (currentPage - 1) * size;
const end = start + size;
const pageinatedData = posts.slice(start, end);
return {
code: 200,
msg: 'success',
items: pageinatedData,
pagination: {
currentPage,
limit: size,
total,
totalPage: Math.ceil(total/size),
}
}
}
}
]
这个代码首先生成 45 条假帖子数据,然后在响应函数中解析查询参数(page 和 limit),实现分页逻辑:
- 使用 parseInt 转换参数,确保数值有效。
- 计算起始索引 start 和结束索引 end。
- 通过 slice 方法截取当前页数据。
- 返回包含 items(当前页数据)和 pagination(分页信息)的对象。
注意,返回格式调整为 { code: 200, msg: 'success', items: ..., pagination: ... },这比接口文档中的简单 { status: 200, list: ... } 更丰富,便于前端处理分页 UI。如果实际接口不同,可以轻松修改。
在 Vite 项目中,需要配置 vite-plugin-mock 来加载这个文件。通常在 vite.config.js 中添加插件:
JavaScript
import { defineConfig } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig({
plugins: [
viteMockServe({
mockPath: 'mock', // mock 文件目录
localEnabled: true, // 开发环境启用
prodEnabled: false, // 生产环境禁用
}),
],
})
这样,在开发模式下,axios 请求 /api/posts 会被 Mock.js 拦截,返回模拟数据。
Axios 配置与 API 封装
接下来,我们配置 axios 以支持 mock。在 src/api/config.ts 中设置 baseURL 为本地地址:
TypeScript
import axios from 'axios';
// 接口地址都以/api开始
axios.defaults.baseURL = 'http://localhost:5173/api'
// axios.defaults.baseURL = 'http://douyin:5173/api'
export default axios;
这里,开发环境使用 http://localhost:5173/api ,生产环境可以切换到真实后端(如注释中的地址)。这种方式确保 mock 无缝过渡。
然后,在 src/api/posts.ts 中封装 fetchPosts 函数:
TypeScript
import axios from './config';
import type { Post } from '@/types';
export const fetchPosts = async (page: number = 1, limit: number = 10) => {
try {
const response = await axios.get('/posts', {
params: {
page,
limit
}
});
console.log(response);
return response.data;
} catch(err) {
}
}
这个函数使用 axios 发送 GET 请求,传递 page 和 limit 参数。注意,catch 块目前为空,在实际开发中应添加错误处理,如返回默认数据或抛出错误。同时,types/Post 来自 types/index.ts,定义了 Post 接口:
TypeScript
export interface User {
id: number;
name: string;
avatar?: string;
}
export interface Post {
id: number;
title: string;
brief: string; // 简介
publishedAt: string; // 发布时间
totalLinkes?: number;
totalComments?: number;
user: User;
tags: string[];
thumbnail: string; // 缩略图
pics?: string[];
}
接口中 totalLinkes 可能是拼写错误,应为 totalLikes,但为了保持一致,我保留原样。如果是笔误,可以在文章中指正:在类型定义中,totalLinkes 可能意为 likes,但建议改为 totalLikes 以提高可读性。
Zustand 状态管理与数据加载
在 React 项目中,使用 Zustand 管理全局状态非常高效。我们创建一个 store/home.ts 来存储 banners 和 posts:
TypeScript
import { create } from 'zustand';
import type { SlideData } from '@/components/SlideShow'
import type { Post } from '@/types';
import { fetchPosts } from '@/api/posts';
interface HomeState {
banners: SlideData[];
posts: Post[];
loadMore: () => Promise<void>
}
const useHomeStore = create<HomeState>((set) => ({
banners: [{
id: 1,
title: "React 生态系统",
image: "https://images.unsplash.com/photo-1633356122544-f134324a6cee?q=80&w=2070&auto=format&fit=crop",
},
{
id: 2,
title: "移动端开发最佳实践",
image: "https://img.36krcdn.com/hsossms/20260114/v2_1ddcc36679304d3390dd9b8545eaa57f@5091053@ai_oswg1012730oswg1053oswg495_img_png~tplv-1marlgjv7f-ai-v3:600:400:600:400:q70.jpg?x-oss-process=image/format,webp",
},
{
id: 3,
title: "百度上线七猫漫剧,打的什么主意?",
image: "https://img.36krcdn.com/hsossms/20260114/v2_8dc528b02ded4f73b29b7c1019f8963a@5091053@ai_oswg1137571oswg1053oswg495_img_png~tplv-1marlgjv7f-ai-v3:600:400:600:400:q70.jpg?x-oss-process=image/format,webp",
}],
posts: [],
loadMore: async () => {
const { items } = await fetchPosts();
}
}))
export default useHomeStore;
这里,banners 是静态轮播数据,posts 初始化为空数组。loadMore 函数调用 fetchPosts 获取数据,但存在一个问题:它没有使用 set 更新 posts 状态!这会导致数据加载后无法反映到组件中。优化建议:在 loadMore 中添加 set:
TypeScript
loadMore: async () => {
const data = await fetchPosts();
set({ posts: data.items });
}
此外,如果需要支持加载更多(load more),可以添加 page 变量,并实现无限滚动或按钮加载。
React 组件集成
最后,在 src/pages/Home.tsx 中使用 store 渲染页面:
typescriptreact
import Header from '@/components/Header'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import SlideShow, {type SlideData} from '@/components/SlideShow';
import useHomeStore from '@/store/home'
import { useEffect } from 'react';
const Home = () => {
const { banners, posts, loadMore } = useHomeStore();
useEffect(() => {
loadMore();
}, []);
return (
<>
<Header title="首页" showBackBtn={true}></Header>
<div className="p-4 space-y-4">
<SlideShow slides={banners}/>
<Card>
<CardHeader>
<CardTitle>
欢迎来到React Mobile
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
这是内容区域
</p>
</CardContent>
</Card>
<div className="grid grid-cols-2 gap-4">
{
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,25, 26].map((i, index) => (
<div key={index} className="h-32 bg-white rouded-lg
shadow-sm flex items-center justify-center border
">
Item {i}
</div>
))
}
</div>
</div>
</>
)
}
export default Home;
优化点:
- 错误处理:在 mock 中添加更多错误场景模拟,如 404 或 500。
- 无限加载:在 store 中添加 page 状态,实现 loadMore 时递增 page。
- 类型安全:使用 TypeScript,确保所有接口一致。
- 性能:如果数据量大,考虑虚拟列表渲染 posts。
总结
通过 Mock.js,我们在 Vite + React 项目中轻松模拟了后端 API,实现了帖子列表的分页功能。这种方法适用于任何前后端分离项目,帮助开发者高效协作。实际应用中,根据接口文档调整 mock 逻辑,就能应对复杂场景。