项目标题是“Mock 使用方式 + 在 Vue 项目中使用 Mock”,这是个非常贴近日常开发的话题。前后端分离做了这么多年,Mock 早就不是“临时凑合”的工具了,而是整个研发流程里不可或缺的一环。你在本地开发时如果还在等后端接口、或者每次都要手动改 response,那这篇文章应该能帮你省下不少时间。我在这里把 Mock 的常见思路、在 Vue 项目中的落地方式、以及我实际踩过的坑都梳理一遍,希望能给正在做 Vue 项目、尤其是刚接触 Mock 的朋友一些参考。
1. 先聊明白:Mock 到底在解决什么问题
1.1 前后端分离开发中的“接口等待”困境
做前端开发的人应该都有这种经历:后端接口还没写好,前端页面得先做起来,这时候要么自己写死数据、要么在代码里临时 new 一堆假数据,等后端接口能用了再一个个替换回来。这种方式的痛点很明显——代码里到处都是测试用的硬编码数据,一不小心就漏改一个;联调阶段接口字段对不上,排查半天发现是自己写死的假数据忘删了。
Mock 解决的就是这个问题:在真实接口不可用的时候,用一个模拟层把网络请求“接住”,返回我们预先准备好的数据。前端代码从头到尾都走真实的请求逻辑,只是请求被拦截了,数据来源从后端数据库变成了 mock 数据。这样前端开发不被后端阻塞,联调时只要把 mock 关掉,请求自然就打到真实服务上。
1.2 什么场景下必须引入 Mock
我个人的经验是,以下几种场景特别需要 Mock:
- 后端接口还没开发完,但前端要并行开发,这是最常见的情况。
- 某些接口依赖复杂的业务状态,比如登录态、支付回调、异常流程,用真实环境很难稳定复现,用 Mock 可以轻松模拟各种分支。
- 压测、演示、UI 走查时需要稳定的数据和流程,不能依赖后端环境的稳定性。
- 单元测试和端到端测试里,需要隔离外部依赖,Mock 是标准做法。
如果只是偶尔一两个接口没有,写死数据确实快,但一旦接口多起来、项目周期长,一套规范的 Mock 方案会让整个开发过程舒服很多。
2. Mock 的常见实现思路与方案选型
2.1 从“代理拦截”到“代码模拟”的几种主流方式
Mock 的实现方式五花八门,但归根结底就是一句话:在请求到达真实后端之前,把请求拦下来,返回预设数据。区别在于“在哪里拦”以及“怎么拦”。
我梳理了一下,目前主流的方案大致有四类:
| 方案 | 拦截位置 | 典型工具 | 优点 | 缺点 |
|---|---|---|---|---|
| 代理层直改 | 本地代理/抓包工具 | Fiddler、Charles、Whistle | 不需要改代码,能拦真实请求 | 配置繁琐,团队协作困难,不能提交到仓库 |
| 框架内置拦截 | 前端代码库 | Axios 拦截器、自定义 request 封装 | 实现简单,可控性强 | 侵入业务代码,需要写大量的拦截逻辑 |
| 独立 Mock 服务 | 独立进程(本地或远端) | json-server、Mock.js + Express | 数据集中管理,可模拟真实网络环境 | 需要单独维护一个服务,增加项目复杂度 |
| 浏览器层拦截 | Service Worker | MSW(Mock Service Worker) | 最接近真实网络,不侵入业务代码 | 概念相对新,需要一定的学习成本 |
每一种方案都有自己适合的场景。如果是一个临时性的 Demo,直接在代码里用 axios 拦截器挡一下就完事;如果是正经项目、而且要长期维护,我推荐在后端接口规范明确的前提下,用工具化的 Mock 方案。
2.2 为什么我更推荐在构建工具层面做 Mock
在 Vite 出现之前,Vue 项目里最流行的 Mock 方案是 webpack-dev-server 的 before 钩子,本质上是在 dev server 里加一层中间件。Vite 出来后,社区里出现了 vite-plugin-mock 这样的插件,原理类似但用法更简单,直接在 Vite 配置里声明 mock 文件路径,剩下的插件都帮你处理了。
这种方式的好处是:
- 和前端代码分离:Mock 数据放在独立的目录里,不污染业务代码。
- 和真实请求无缝切换:开发时走 mock,请求的 URL 和真实接口一致,联调时去掉插件或改个环境变量就切回真实接口,不需要改动业务代码。
- 支持热更新:改 mock 数据时不用重启服务,刷新页面就能生效,这效率感受过就回不去了。
- 和 TypeScript 配合好:可以使用 TS 编写 mock 数据,享受类型提示。
对比之下,axios 拦截器的方案虽然灵活,但每次都要写 if 判断、要自己去匹配 URL,Mock 逻辑多了之后维护成本很高。而代理工具改响应数据又只对个人本地生效,没法提交到代码仓库里给团队共用。
所以项目级 Mock 方案,我的结论非常明确:Vue3 + Vite 项目优先用 vite-plugin-mock,Vue2 + webpack 项目用 webpack-dev-server 的 before 钩子或者 express 中间件。这两种都是在“构建工具/dev server”层面拦截,团队共享、逻辑统一、切换灵活。
2.3 顺带聊聊 msw 和 fiddler 这两个高频词
在一些热词里看到了 msw 和 fiddler mock 不生效的搜索,我多提两句。msw 是最近几年挺火的前端 Mock 库,它用 Service Worker 在浏览器网络层面拦截请求,理论上最接近真实网络环境,连请求头、Cookie、CORS 都会被模拟。但正因为它拦截的是浏览器网络层,遇到一些特殊环境(比如 Service Worker 不支持或受控策略限制)就会出各种问题,排查起来相对麻烦。我的建议是常规业务项目先用构建工具层方案,如果哪天你需要在上线环境动态 Mock、或者要模拟非常底层的网络异常,再考虑 msw。
fiddler 这类代理工具改响应数据不生效,90% 的情况是因为没装根证书、或者抓的是 HTTPS 请求但没开启解密,另一小半是因为前端代码做了强缓存或者请求被 Service Worker 接管了。代理工具的定位是“辅助调试”,不是“团队基建”,所以不建议把它当作项目 Mock 的主方案。
3. 核心原理拆解:一个 Mock 请求“从发出到返回”的完整路径
3.1 请求拦截的本质是“URL 匹配 + 响应替换”
不管你用哪种 Mock 方案,核心机制都是一样的:当一个请求发出时,Mock 层先拿到请求的 method、URL、参数等信息,去预设的 Mock 数据里找有没有匹配的规则;找到则直接返回 Mock 数据,没找到就放行到真实后端。
听起来很简单,但这里有几个细节非常影响实际使用体验:
- 精确匹配 vs 模糊匹配:有些接口路径里有动态参数,比如
/api/user/123,Mock 工具要支持这种模式匹配。vite-plugin-mock 内部用的 path-to-regexp(就是 vue-router 用的那套路径匹配),所以写'/api/user/:id'就能匹配任意 id。 - 方法区分:同一个 URL,GET 和 POST 返回的数据应该是不同的,规则里必须能指定 method。
- 请求体和查询参数:mock 数据有时候需要根据请求内容动态生成,比如分页查询时根据
page和pageSize返回不同的数据切片,这就需要在 mock 函数里读取 request 对象。
3.2 Mock.js 的随机数据生成逻辑
很多人在 vite-plugin-mock 里还会配合 Mock.js 使用,它最大的价值在于可以快速生成符合规则的大量模拟数据。比如Mock.mock('@cname')会返回一个随机中文名、@integer(1, 100)返回 1 到 100 的随机整数、@image('200x100')返回一张随机图片链接。这些在需要造列表数据、图表数据的时候特别好使,省得自己写死一组数据,看着就假。
不过要提醒一下:Mock.js 的随机结果在每次请求时都不一样,这在调试时可能引起困惑。比如你点击页码 2,返回的却不是第一页的后续数据,因为数据本来就是随机生成的。我一般会在使用 Mock.js 生成列表数据时,用Array.from提前生成一份固定数据数组,再根据分页参数切片返回,这样既能保证数据量充足,又能保证翻页逻辑是自洽的。
4. 实操过程:在 Vue 3 + Vite 项目里集成 vite-plugin-mock
4.1 环境准备与安装依赖
在开始之前,确认你的项目是 Vite 构建的。这里以目前最常用的 Vue 3 + Vite 组合为例,如果你还是 Vue 2 + webpack,思路类似,但插件要换成 webpack-dev-server 的 before 钩子,细节上略有差异。
首先安装依赖:
npm install vite-plugin-mock mockjs -D如果你使用 TypeScript,可能还需要安装类型声明:
npm install @types/mockjs -D然后修改vite.config.ts,引入插件并配置:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { viteMockServe } from 'vite-plugin-mock' export default defineConfig({ plugins: [ vue(), viteMockServe({ mockPath: 'mock', enable: true, watchFiles: true, logger: true, }), ], })mockPath是指定 mock 文件存放的目录,enable是开启开关,watchFiles表示文件变化后自动热更新,logger会在终端打印 mock 请求日志。这几个配置项我觉得是起步最常用的,先配这些就够了。
4.2 编写第一个 Mock 接口
在项目根目录创建mock文件夹,新建一个用户模块的 mock 文件,例如mock/user.ts:
import { MockMethod } from 'vite-plugin-mock' export default [ { url: '/api/user/login', method: 'post', response: ({ body }) => { const { username, password } = body if (username === 'admin' && password === '123456') { return { code: 0, message: 'success', data: { token: 'mock-token-123456', username: 'admin', nickName: '管理员', roles: ['admin'], }, } } return { code: -1, message: '用户名或密码错误', data: null, } }, }, { url: '/api/user/info', method: 'get', response: ({ headers }) => { // 这里可以模拟根据请求头里的 token 返回不同用户信息 return { code: 0, message: 'success', data: { userId: 1, username: 'admin', avatar: 'https://example.com/avatar.png', permissions: ['view:order', 'edit:order'], }, } }, }, ] as MockMethod[]注意,这里的response函数会接收一个包含body、query、headers等信息的参数对象,你可以根据请求内容返回动态数据。比如登录接口,就可以根据传入的用户名密码做逻辑判断,这比固定返回一组数据更贴合真实业务场景。
4.3 前端请求层的无缝衔接
在接入 Mock 之前,我建议先把项目里的请求工具类封装好。如果是新项目,axios 是绕不开的,简单封装如下:
import axios from 'axios' import { message } from 'ant-design-vue' const service = axios.create({ baseURL: '/api', timeout: 10000, }) service.interceptors.request.use((config) => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( (response) => { const res = response.data if (res.code !== 0) { message.error(res.message || '请求失败') return Promise.reject(new Error(res.message || '请求失败')) } return res }, (error) => { message.error('网络异常,请稍后重试') return Promise.reject(error) }, ) export default service有了这个封装之后,业务代码调用请求就和真实后端完全一致:
// api/user.ts import request from '@/utils/request' export interface LoginParams { username: string password: string } export function login(data: LoginParams) { return request.post('/user/login', data) }这里有个很容易搞混的点:axios 的 baseURL 是/api,而 mock 文件里写的 URL 是/api/user/login,最后前端请求的路径是/api/user/login,Mock 层的规则也要写成完整路径/api/user/login,而不是让 baseURL 和 mock 的 url 错位拼接。我自己就踩过这个坑,mock 里写/user/login,结果死活匹配不上,因为 mock 插件匹配的是完整请求 URL。
4.4 按环境切换 Mock 开关
实际项目里,我们通常希望在开发环境开启 Mock,在构建上线时关闭。我的做法是利用 Vite 的环境变量来控制enable字段:
viteMockServe({ mockPath: 'mock', enable: process.env.NODE_ENV === 'development', watchFiles: true, logger: true, })但这里有个细节要注意:viteMockServe的enable配置在构建时是静态判断的,如果你希望在某些测试预发环境也临时打开 Mock,可以在package.json里新增一个脚本:
{ "scripts": { "dev": "vite", "dev:mock": "vite --mode mock", "build": "vite build" } }然后在项目根目录创建.env.mock文件:
NODE_ENV=development VITE_USE_MOCK=true在vite.config.ts里读取它:
viteMockServe({ mockPath: 'mock', enable: process.env.VITE_USE_MOCK === 'true', watchFiles: true, logger: true, })这样npm run dev是正常开发,npm run dev:mock是强制开启 Mock,不需要改任何代码。
4.5 如何关闭 Mock 而不改动业务代码
有时候联调阶段希望切换部分接口到真实后端、部分接口继续用 Mock,这种场景怎么做?我的经验是不要同时启用“Mock 层拦截 + 真实接口”,因为两者会相互干扰,很难排查问题。更可控的做法是:在 Mock 数据里对还没有就绪的接口保留规则,已就绪的接口把规则删除或注释掉,然后重启 dev server 即可。
如果你想要“按需开关”,可以利用 vite-plugin-mock 的configPath或者针对不同目录做配置,但这就复杂了。对大多数团队来说,最直接的方式就是“开发阶段全部 Mock,联调阶段全部真实”,中间过渡期可以在 mock 文件里加一个全局开关变量来控制哪些接口返回 Mock。
5. 常见问题与排查技巧实录
5.1 Mock 数据不生效的排查路径
这是大家问得最多的一个问题。结合我自己的定位经验,整理一份排查清单:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 请求能发出去,但返回 404 | Mock 文件里的 URL 和前端请求的 URL 不一致 | 对比完整请求路径,注意 baseURL 前缀 |
| 返回 200 但不是 Mock 数据 | 请求没被 Mock 插件拦截,打到了真实后端 | 确认插件 enable 开启、mockPath 目录存在 |
| 改了 Mock 数据不生效 | watchFiles 未开启,或 node_modules 缓存 | 打开 watchFiles,重启 dev server |
| 请求在浏览器里显示 pending | Mock 插件抛了异常,服务端崩溃 | 看终端日志,检查 mock 文件语法 |
| 接口返回 Mock 数据但业务报错 | 字段结构和真实接口不一致 | 打开终端 logger 看 mock 返回的具体数据结构 |
| 构建后接口 404 | Mock 在生产环境未启用或不应启用 | 确认 NODE_ENV 判断是否正确 |
5.2 处理带有 Token 鉴权的 Mock 接口
很多接口需要在请求头里带 token,Mock 里也要模拟这一点,否则前端在联调时没法验证“未登录”和“登录过期”两个分支。
我在 mock 文件里一般这样写:
{ url: '/api/user/order/list', method: 'get', response: ({ headers }) => { const token = headers.authorization if (!token) { return { code: 401, message: '未登录或登录已过期', data: null, } } return { code: 0, message: 'success', data: [ // 订单列表数据 ], } }, }这样前端在 token 缺失时的跳转逻辑就能在本地提前验证,不用等后端。
5.3 模拟网络延迟与异常分支
真实项目中,接口不可能总是瞬时响应。如果你想看 loading 效果、或者测试并发请求时的表现,可以在 mock 里主动加延迟:
async response() { await new Promise((resolve) => setTimeout(resolve, 1000)) return { code: 0, data: [] } }同样,模拟接口抛错也很方便,例如返回一个 500 状态码:
{ url: '/api/system/error', method: 'get', statusCode: 500, response: () => ({ code: 500, message: '服务器内部错误', }), }这时候 axios 的拦截器就会走到 error 分支,你可以提前调好全局错误提示和上报逻辑。
5.4 和联调阶段衔接的独家建议
踩了几次坑之后,我总结了一个特别实用的切换流程,适用于大多数中小团队的 Vue 项目:
在项目里新建一个mock/index.ts,统一导出所有 mock 模块:
import userMock from './user' import orderMock from './order' export default [...userMock, ...orderMock]在vite.config.ts里确保mockPath指向这个文件夹,然后在联调阶段不要急着删 mock 文件,而是把enable设为false。这样如果发现真实接口有问题,随时切回 mock 环境验收前端逻辑,还能对比到底是后端问题还是前端问题。
如果接口规范有变化,前端需要改请求参数和数据结构,但后端还没改完,这时候 Mock 就变成了一个天然的“合同测试”:前端严格按接口文档生成 Mock,联调时一旦发现字段不一致,大概率是后端实现和文档有出入,这种问题在 Mock 阶段就能提前暴露。
6. 进阶扩展:从 Mock 到接口文档自动化的思路
6.1 用 Mock 数据反哺前后端协作流程
用了 Mock 一段时间之后,你会发现它不只是“开发期的临时替代品”,还能反过来规范接口设计。接口文档里通常只写字段名和大概含义,但前端最关心的其实是每个字段的取值样例、边界情况、错误码。Mock 文件里的 Response 就是你最具体的数据样例。如果 Mock 能对齐接口文档,联调时就能少很多返工。
我建议在 mock 文件里写清楚注释,标注哪些字段是枚举值、哪些字段可能为 null、哪些字段在不同状态下有不同结构。这些注释在后续维护时非常值钱。
6.2 结合 Postman/Apifox 使用 Mock
有些团队习惯用 Apifox 或 Postman 做接口管理,这类工具自带 Mock 数据功能。它们的思路是:后端定义好接口文档后,工具根据字段类型自动生成随机 Mock 数据,前端可以在工具里预览数据结构,然后把这些数据复制到项目 mock 文件里。
个人经验是:如果团队已有成熟的接口管理平台,项目内的 mock 还是建议以本地文件为准,因为本地 mock 能模拟逻辑(比如登录判断),而平台型 mock 大多数只能返回静态数据。两者配合起来,平台负责提供字段样例,本地负责处理业务逻辑,这才是效率最高的打法。
6.3 vite-plugin-mock 之外的备选方案
如果在某些项目里 vite-plugin-mock 用得不顺手,也有其他选择:
- 用
vite-plugin-mock-dev-server,它基于 Vite 的 connect 中间件,支持直接在注释里写响应数据,对后端同学比较友好。 - 直接写一个 Express 中间件挂在 Vite dev server 上,照样能实现拦截。
- 如果项目里有 Node 层(比如 Next.js、Nuxt 的 server 端),也可以直接在服务端中间件里做 Mock。
方案选型不需要我多推荐,关键是团队用着顺、符合项目结构。
7. 写在最后的几点经验
有一说一,Mock 本身并不是什么复杂技术,但把一个 Mock 体系用顺,确实需要一点前期规划。我在实际项目中最大的体会是:Mock 的目录结构要有“业务模块感”,不要把所有接口塞在同一个文件里,否则接口多了之后维护成本陡增。我习惯按业务域拆分,比如user.ts、order.ts、goods.ts,每个文件里只放这个模块相关的接口,文件顶部写清楚模块说明。
另外,Mock 数据写完了,记住“真实感”很重要。真实接口返回的数据不会总是一尘不变的,有的字段可能为空、有的列表可能超长、有的错误码很冷门。如果你想让前端处理得更稳,在 Mock 阶段就多模拟一些边缘情况,这比上真实环境之后再排查要舒服得多。
最后再分享一个小技巧:在 mock 文件里加一行日志打印,看看每次请求的 body、query、headers 到底是什么,你会发现很多“接口对不上”的问题,根源都是前端传参和后端预期不一致,在 Mock 阶段把参数打出来,比在后端日志里翻要直观得多。
Mock 用好了,是真的能让开发体验提升一个档次。如果这篇文章里提到的方案对你项目有帮助,建议直接照着落地试一下,不要只收藏不动手。遇到具体问题,欢迎在实际操作中多调试几轮,很多细节只有亲手踩过才会有更深的理解。