在现代前端工程化开发中,前后端分离已成为标准模式。我们不再依赖后端“先写完接口”,而是基于约定的 API 文档,各自并行推进。
但问题来了:
后端还没给接口,我怎么写列表页?没有数据,页面就是一片空白。
这时候,一个看似简单却极为关键的技术浮出水面——接口数据模拟(Mock)。
今天继续推进我们的实战项目,带你一步步理解:
如何使用 Mock.js 搭建本地假数据服务,支撑前端独立开发,并深入剖析其背后的设计思想、实现细节与注意事项。
一、为什么需要 Mock?不是联调才开始吗?
很多刚入行的同学会误以为:“前端做完样式就等后端。”
但实际上,在成熟的团队协作中,前端不应该等待后端。
举个例子:
你要开发一个「文章列表页」,需求如下:
- 显示标题、摘要、作者头像、发布时间;
- 展示标签(如“前端”、“算法”);
- 支持分页加载,每页10条;
- 点击进入详情页。
此时,如果等到后端把 /api/posts?page=1&limit=10 接口写好再开工,你的进度就被锁死了。
而如果你能提前定义好这个接口的响应格式,自己造一批结构一致的数据,就可以立刻开始:
- 调试组件渲染;
- 验证分页逻辑;
- 测试错误边界处理;
- 甚至完成初步性能优化。
这正是 Mock 的核心价值:让前端摆脱对后端的实时依赖,提升开发自主性与效率。
二、Mock.js 是什么?它解决了哪些问题?
Mock.js 是一个用于生成随机数据、拦截 Ajax 请求的前端库。它的最大特点是:
- 使用简洁语法生成符合规则的假数据;
- 可模拟复杂嵌套结构(如用户信息、图片数组);
- 支持动态函数逻辑(比如随机选标签);
- 结合构建工具可在开发环境自动启用,上线前关闭。
但它本身并不直接提供 HTTP 服务。我们需要借助像 vite-plugin-mock 这样的插件,将 Mock 数据挂载到指定路由上,使其表现得和真实接口一模一样。
三、实战:搭建一个可分页的文章列表 Mock 服务
我们以一个常见的内容平台为背景,来实现 /api/posts 接口的 Mock。
第一步:确定接口契约(API Contract)
在动手之前,必须和后端达成一致。哪怕只是口头约定,也要明确:
GET /api/posts?page=1&limit=10
响应:
{
"code": 200,
"msg": "success",
"items": [...],
"pagination": {
"total": 45,
"current": 1,
"limit": 10,
"totalPages": 5
}
}
这就是所谓的“接口契约”。只要双方遵守这个结构,后续切换真实接口时就能无缝对接。
第二步:生成符合业务特征的假数据
我们现在要用 Mock.js 构造 45 条具备真实感的文章数据。
1. 定义基础字段
{
title: '@ctitle(8,20)', // 中文标题,8~20字
brief: '@ctitle(20,100)', // 摘要作为短文本
totalComments: '@integer(1,30)', // 评论数在合理范围
totalLikes: '@integer(0,300)', // 点赞数有差异
publishedAt: '@datetime("yyyy-MM-dd HH:mm")', // 时间格式统一
id: '@increment(1)' // 自增 ID,便于调试
}
这些 @ 开头的写法是 Mock.js 的占位符语法,非常直观。相比手动 new Array 填充对象,效率高出许多。
2. 复杂结构处理:用户信息 + 图片资源
文章通常关联作者:
user: {
id: '@integer(1,1000)',
name: '@cname()',
avatar: '@image(300x200)'
}
这里 @cname() 生成中文姓名,@image(300x200) 返回一个 base64 编码的占位图 URL,可以直接用于 <img src>,无需额外准备图片资源。
再比如封面图和图集:
thumbnail: '@image(300x200)',
pics: [
'@image(300x200)',
'@image(300x200)',
'@image(300x200)'
]
虽然都是静态图,但视觉上足够支撑 UI 开发。
3. 特殊逻辑:标签随机选取
标签不能全一样,否则测试不到多态性。我们可以这样设计:
const tags = ["前端", "后端", "AI", "职场", "副业", "面经", "算法"];
tags: () => Mock.Random.pick(tags, 2)
注意这里是函数形式,确保每次生成都独立选择两个不同标签。如果是直接写 tags: ['前端','算法'],那所有数据都会一样,失去模拟意义。
第三步:支持分页查询能力
光有数据还不够,接口还要能处理参数。
我们需要解析请求中的 page 和 limit,返回对应的子集。
response: ({ query }) => {
const { page = '1', limit = '10' } = query;
const currentPage = parseInt(page, 10);
const size = parseInt(limit, 10);
// 参数校验
if (isNaN(currentPage) || isNaN(size) || currentPage <= 0 || size <= 0) {
return { code: 400, msg: 'Invalid page or pageSize' };
}
const total = posts.length;
const start = (currentPage - 1) * size;
const end = start + size;
const paginatedData = posts.slice(start, end);
return {
code: 200,
msg: 'success',
items: paginatedData,
pagination: {
total,
current: currentPage,
limit: size,
totalPages: Math.ceil(total / size)
}
};
}
这段代码的关键点在于:
- 类型安全转换:URL 参数永远是字符串,必须
parseInt并做合法性判断; - 边界控制:避免负数或零导致数组越界;
- 分页计算清晰:起始索引 =
(当前页 - 1) * 每页数量; - 返回完整元信息:总数、总页数等帮助前端控制分页器状态。
这样一来,无论你访问 /api/posts?page=2&limit=10 还是 page=3&limit=5,都能拿到正确的结果。
四、如何接入项目?Vite 下的集成方案
有了 Mock 数据,还需要让它跑在正确的路径上。
我们使用 vite-plugin-mock 插件来激活 Mock 功能。
1. 安装依赖
pnpm add mockjs -D
pnpm add vite-plugin-mock -D
2. 配置 vite.config.ts
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, // 开发环境启用
prodEnabled: false, // 生产环境禁用
injectCode: `
import { setupProdMockServer } from '../mock/_createProductionServer';
setupProdMockServer();
`,
})
]
});
⚠️ 注意:生产环境默认不开启 Mock。若需在打包后仍使用(如演示环境),可通过
prodEnabled控制,并注入启动代码。
3. 目录结构组织
/src
├── mock/
│ └── posts.js
├── api/
│ └── posts.ts
└── types/
└── index.ts
只要文件导出的是数组或对象,vite-plugin-mock 就会自动注册其中的路由规则。
五、前端调用方式:像请求真实接口一样自然
我们在 api/posts.ts 中封装请求方法:
import axios from './config';
import type { Post } from '@/types';
interface PostsResponse {
items: Post[];
pagination: {
total: number;
current: number;
limit: number;
totalPages: number;
};
}
export const fetchPosts = async (
page: number = 1,
limit: number = 10
): Promise<PostsResponse> => {
try {
const res = await axios.get('/posts', {
params: { page, limit }
});
return res.data;
} catch (err) {
console.error('[fetchPosts] 请求失败', err);
return { items: [], pagination: { total: 0, current: page, limit, totalPages: 0 } };
}
};
你会发现,无论是调用 Mock 还是真实接口,这段代码完全不需要修改。
唯一的区别只是:
- 开发时:
axios请求被拦截,返回 Mock 数据; - 上线后:指向真实域名,获取真实响应。
只要接口结构不变,替换零成本。
六、优点总结:为什么推荐使用这种方式?
| 优势 | 说明 |
|---|---|
| ✅ 提升开发效率 | 前端可独立工作,不卡在联调阶段 |
| ✅ 降低沟通成本 | 所有人都清楚接口长什么样 |
| ✅ 更早发现问题 | 可模拟异常情况(如空数据、超长标题) |
| ✅ 支持多种场景 | 分页、筛选、排序均可提前验证 |
| ✅ 易于维护 | 数据结构集中管理,修改方便 |
更重要的是,这种做法推动团队形成良好的协作习惯——先定接口,再写代码。
七、需要注意的问题与最佳实践
尽管 Mock 很强大,但也有一些陷阱需要注意:
❌ 不要过度拟真
有些人喜欢用 Mock 生成几千条数据测试滚动性能。这其实没必要。Mock 应服务于功能开发,而不是性能压测。真正性能问题要在真实数据环境下暴露。
❌ 忽视类型一致性
务必保证 Mock 返回的数据结构和 TypeScript 类型定义严格匹配。例如:
type Post = {
title: string;
totalLikes: number;
tags: string[];
}
如果你在 Mock 中让 totalLikes 返回字符串,编译不会报错,但运行时报错,这就失去了类型保护的意义。
✅ 建议:把 Mock 数据抽象成 Schema
可以单独抽离一份 post.mock.schema.ts:
export const postSchema = {
title: '@ctitle(8,20)',
totalLikes: '@integer(0,300)',
tags: () => Mock.Random.pick(tagList, 2),
// ...
};
然后在多个地方复用,提高可维护性。
✅ 建议:保留切换开关
可以在项目中加一个全局变量或配置项,允许临时关闭 Mock,直连后端测试:
// .env.development
VITE_USE_MOCK=true
通过环境变量控制是否启用 Mock 插件,方便快速对比。
八、结语:善用 Mock“前置验证”,独立闭环
很多人误解 Mock 是“骗自己”。其实恰恰相反:
Mock 是一种主动设计行为,是对接口契约的具象化表达。
当你能准确描述出“我需要什么样的数据”,说明你已经理解了业务逻辑。而当你能模拟出分页、错误、加载等各种状态时,你的代码健壮性自然提升。
拿起 Mock.js,从今天起做一个能独立闭环交付的前端开发者。
附:学习建议路线
- 先读接口文档,画出预期响应结构;
- 用 Mock.js 写出对应数据模板;
- 在前端调用并渲染;
- 等后端完成后,仅修改 baseURL 即可切换;
- 对比差异,完善边界处理。
这才是真正的“前后端分离”开发之道。