☰
前端 Mock 数据方案全解析:MSW、抓包与契约驱动
2026/10/1 1:19:45 网站建设 项目流程

做过几年前端的人大概都经历过这种场面:需求评审刚过,设计稿还在改,接口文档只写了个标题,产品经理已经在群里问“明天能不能先看个演示”。这时候你要么干等,要么自己把数据造出来。前端 mock 数据这件事,说到底就是在这段空白期里让页面先跑起来——它不是什么偷懒的小技巧,而是把前端从“等接口”的被动状态里拽出来的常规工程手段。从最土的本地 JSON 文件,到请求层的拦截,再到抓包工具改写响应、后端契约驱动的 Mock 服务器,每条路我都趟过,也都在不同的项目阶段吃过各自的亏。这篇就把这些方式挨个拆开讲清楚:它们各自的原理是什么、什么阶段该用哪个、参数怎么定、坑在哪里、出了问题按什么顺序排查。刚入行的同学可以照着抄作业,做过两三年的也能在里面找到一些能直接塞进现有项目的开关设计和目录约定。读完之后你至少应该能回答一个问题:手上这个项目,到底该选哪种 mock 方式,为什么。

1. 前端 Mock 到底在解决什么问题

先把这个概念摆正。很多人一提 mock 就想到“造假数据”,其实它解决的是三个完全不同的诉求,混在一起谈就很容易选错方案。第一种是并行开发:后端还没写完,前端要先把页面结构和交互逻辑跑通,此时的 mock 数据只需要形状对、字段够用就行。第二种是边界场景构造:接口本身已经有了,但你想看空列表、超长昵称、金额为 0、时间跨年这些情况,真实环境里很难手动造出来。第三种是稳定复现:某个 bug 只在特定的响应组合下出现,你需要让接口每次都返回一模一样的数据,方便断点调试。

这三种诉求决定了你该用哪一层的手段。并行开发适合用请求拦截或者 Mock 服务器,因为改起来快、大家共享一份;边界场景构造适合在拦截层或者浏览器 DevTools 的响应覆盖里做,因为不需要动代码就能临时改一个字段;稳定复现则更倾向于本地代理或者抓包工具的固定响应,因为它能锁死整个响应体。

1.1 先想清楚数据是给谁看的

有个判断标准特别实用:这份 mock 数据是要提交到代码仓库、给整个团队共用的,还是只给你自己临时看几眼的?前者必须走工程化的路子,放在约定的目录下,有类型声明,有开关控制,随时能关掉;后者怎么方便怎么来,DevTools 里改一下响应体、本地起个静态文件,十分钟搞定,没必要为了“规范”给自己加负担。

我见过不少团队在这件事上走极端。有的把所有 mock 数据都硬编码在组件里,上线前清洗不干净,法务一个字段一个字段地查;有的又过度设计,专门搭了一套 mock 平台,结果没人维护,半年后文档和实际返回对不上,新人照着文档调半天,最后发现是 mock 数据过期了。合理的做法是按阶段分配:项目初期广撒网,中期收敛到一层统一的拦截,交付前把开关彻底拆掉或者指向真实接口。

1.2 数据不一致的根源在哪里

“数据不一致”这个词在搜索里出现频率很高,但它其实是好几类问题的统称。第一类是字段名和类型对不上:后端返回user_id(下划线),前端按userId(驼峰)取,取出来是undefined,页面显示空白,但控制台又不报错,最难查。第二类是结构和空值语义不同:后端在没有数据时返回null,前端按数组处理做了.map(),直接白屏。第三类是mock 数据和真实数据本身就有偏差:mock 里手机号是 11 位数字字符串,真实数据里带了+86前缀,正则校验就挂了。

这三类里,前两类靠类型声明和约定能解决一大半,第三类必须靠“从真实响应里拷贝一份脱敏样本”来兜底。我的习惯是:任何 mock 数据在落地前,先想办法搞到一条真实响应,字段名、嵌套层级、空值形态全部对齐,然后再把值替换掉。这一步花十分钟,能省掉后面一整天的对不上。

2. 硬编码与本地 JSON:最土但最快的起手式

新手最先接触的肯定是这一种:新建一个data.json,里面塞几个对象,组件里import进来直接用。它谈不上优雅,但在项目刚开始、页面结构还没定下来的时候,效率是最高的。因为它零配置、零依赖、不需要起任何额外服务,鼠标点一下就能看到效果。

合理的组织方式是按业务模块分目录,而不是所有数据堆一个文件。比如mock/user.json、mock/order.json、mock/common/dict.json,字典类数据单独抽出来,因为它在多个页面复用。文件内部建议保留真实接口的完整结构,包括外层那层code、message、data,别只留data里的内容,否则后面切换到真接口时,取值路径要全部改一遍。

2.1 JS 模块导出假数据更适合造边界值

JSON 只能写死值,而.ts模块可以写逻辑。当前端需要造一批有规律的边界数据时,模块导出会比 JSON 好用得多。比如要测一个分页列表,需要 500 条数据,还要覆盖长文本和特殊字符:

// mock/generator/user.ts const NICKNAMES = ['张三', '李四', '王五', '这是一条特别长的昵称用来测试表格换行是否正常']; // 生成过去 N 天内的随机时间戳(毫秒) function randomTimestampWithin(days: number): number { const now = Date.now(); const range = days * 24 * 60 * 60 * 1000; // 天数换算成毫秒 return now - Math.floor(Math.random() * range); } // 用固定种子生成可复现的伪随机数,保证每次刷新数据一致 function createRandom(seed = 20260101) { let s = seed; return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; }; } export function buildUserList(total = 500) { const rand = createRandom(); return Array.from({ length: total }, (_, i) => ({ id: 10000 + i, userId: `U${10000 + i}`, nickname: NICKNAMES[Math.floor(rand() * NICKNAMES.length)], amount: Number((rand() * 10000).toFixed(2)), status: rand() > 0.7 ? 0 : 1, createdAt: randomTimestampWithin(30), })); }

这里有两个细节值得说一下。randomTimestampWithin里的days * 24 * 60 * 60 * 1000是标准的天转毫秒,别写成days * 86400000之外的花样,虽然结果一样,但前者更容易一眼看懂,也方便改成年、小时。而那个createRandom是线性同余伪随机,目的是让每次刷新页面时数据保持一致——真随机会导致你改了一个字段、刷新一下,整张表全变了,根本没法对比调试。这个坑我踩过不止一次,后来所有需要随机值的 mock 都换成固定种子。

2.2 硬编码方案的三个硬伤

第一个硬伤是跟真实请求流程脱节。组件里直接import数据,意味着拿不到 loading 状态、错误状态、超时这些真实场景。等切换到真接口那天,会发现加载动画根本没写、失败重试也没做,只能临时补。

第二个硬伤是误上生产。静态 import 会被构建工具打进包里,如果没人清理,线上会存在一堆用不到的假数据。我见过最离谱的一次,一个字典文件里带着测试用的内部人员名单,被打进了生产 bundle,虽然没暴露到页面上,但通过 Source Map 就能翻出来。

第三个硬伤是无法覆盖分页、搜索、排序这类交互。用户点了第二页,数据还是第一页那几百条,测试根本没法往下走。所以硬编码的定位要清楚:它只适合“静态展示型页面”,一旦涉及请求交互,就该换方案了。

注意:硬编码的数据文件不要放在src下跟业务代码混在一起,统一放到src/mock/data/这种带明显标识的目录,方便上线前用脚本扫描清理,也方便做代码审查时一眼识别。

3. 请求拦截层:MSW 为什么成了主流选择

请求拦截的核心思路是:不动业务代码,在底层把真实的网络请求截住,返回你预先准备好的数据。这样组件里写的还是axios.get('/api/user/list'),切到真接口时一行代码都不用改。这是它比硬编码强的地方,也是我认为在“并行开发”阶段最值得投入的一层。

拦截的技术实现经历过几代演进。最早是重写XMLHttpRequest的open和send方法,或者包装fetch,思路简单,但对axios的适配器、umi-request这类二次封装的库不太友好,经常出现“拦截了但没生效”。后来出现 Service Worker 方案,也就是 MSW(Mock Service Worker)走的路子,它在浏览器和页面之间加了一层,能真正拦截网络请求,连 DevTools 的 Network 面板里都能看到请求记录。

3.1 手写 XHR 拦截:理解原理用得上

虽然不推荐在生产项目里手写,但理解它的原理有助于排查“为什么 mock 没生效”。下面这段是最小实现:

// 仅用于理解原理,不建议在正式项目中使用 (function interceptXHR() { const OriginalXHR = window.XMLHttpRequest; const mockRules = [ { test: (url) => /\/api\/user\/list/.test(url), response: { code: 0, message: 'ok', data: { list: [], total: 0 } }, }, ]; function MockXHR() { const xhr = new OriginalXHR(); const originalOpen = xhr.open; const originalSend = xhr.send; xhr.open = function (method, url, ...rest) { this.__url = url; return originalOpen.call(this, method, url, ...rest); }; xhr.send = function (body) { const rule = mockRules.find((r) => r.test(this.__url)); if (!rule) return originalSend.call(this, body); // 手动造出一次成功的响应 setTimeout(() => { Object.defineProperty(this, 'readyState', { value: 4 }); Object.defineProperty(this, 'status', { value: 200 }); Object.defineProperty(this, 'responseText', { value: JSON.stringify(rule.response) }); this.dispatchEvent(new Event('readystatechange')); this.dispatchEvent(new Event('load')); }, 100); }; return xhr; } window.XMLHttpRequest = MockXHR; })();

这段代码能说明三个关键点:拦截必须发生在业务代码请求之前(所以脚本要放在最前面,或者在应用初始化时执行);readyState、status、responseText这几个属性要能被读到;事件要手动触发,否则 Promise 永远不会 resolve。知道了这些,当你遇到“请求发出去了但 Promise 一直挂起”的情况,就能立刻定位到是事件没派发。

3.2 MSW 在 Vue3 + TS 项目里的落地步骤

MSW 的优势在于它用 Service Worker 在浏览器层拦截,业务代码完全无感知,并且能同时跑在 Node 环境(单元测试)里,一套 handler 两处复用。下面是完整的落地流程。

第一步,装依赖,把 Service Worker 脚本生成到公共目录:

npm i -D msw npx msw init public --save

执行完会在public/下生成mockServiceWorker.js,这个文件必须能被浏览器以根路径访问到,所以放在public或static目录,不能放进src里被打包。

第二步,写 handler。这里以msw2.x 的写法为例,注意 API 跟 1.x 差别很大,网上很多老教程是rest.get,新版已经换成http.get:

// src/mocks/handlers.ts import { http, HttpResponse, delay } from 'msw'; import { buildUserList } from './generator/user'; export const handlers = [ http.get('/api/user/list', async ({ request }) => { const url = new URL(request.url); const page = Number(url.searchParams.get('page') ?? '1'); const size = Number(url.searchParams.get('size') ?? '20'); // 模拟真实网络延迟,方便观察 loading 状态 await delay(300); const all = buildUserList(500); const start = (page - 1) * size; // 分页起始下标 return HttpResponse.json({ code: 0, message: 'ok', data: { list: all.slice(start, start + size), total: all.length, page, size, }, }); }), // 模拟一个失败场景,用于测试错误处理分支 http.post('/api/order/create', async () => { await delay(200); return HttpResponse.json( { code: 40001, message: '库存不足' }, { status: 200 }, ); }), ];

分页那段const start = (page - 1) * size是最容易被写错的地方,第 1 页要从下标 0 开始,写成page * size会导致第一页永远是空的,然后你会以为是 mock 没生效,白白排查半天。

第三步,在入口处按环境变量决定是否启动:

// src/main.ts import { createApp } from 'vue'; import App from './App.vue'; async function bootstrap() { // 只有显式开启 mock 开关时才启动,避免污染测试或预发环境 if (import.meta.env.DEV && import.meta.env.VITE_USE_MOCK === 'true') { const { worker } = await import('./mocks/browser'); await worker.start({ onUnhandledRequest: 'bypass', // 没匹配到的请求直接放行,别报错 }); } createApp(App).mount('#app'); } bootstrap();
// src/mocks/browser.ts import { setupWorker } from 'msw/browser'; import { handlers } from './handlers'; export const worker = setupWorker(...handlers);

onUnhandledRequest这个配置很关键。默认值是warn,意思是没匹配上的请求会在控制台刷警告。项目里静态资源、埋点、CDN 请求一大堆,警告多到你根本看不见真正的信息,所以开发时设成bypass更清爽。但要注意,如果你明明配了 handler 却还是走了真实请求,先把这里临时改成error,它会在请求未匹配时直接抛出异常,能逼你立刻发现规则写错了。

第四步,在.env.development里加开关:

VITE_USE_MOCK=true

这样做的意义在于:mock 的开启变成了一件显式的、可追踪的事。CI 构建默认不带这个变量,就不会被打进去;某个同学本地想看真实数据,改一下环境变量重启即可。

3.3 顺带说下 vite-plugin-mock 这类方案

有些项目用的不是 MSW,而是vite-plugin-mock,它在 Vite 的 dev server 中间件层面拦截,配置写在vite.config.ts里。它的好处是天然在 Node 层,能直接读本地文件、能写日志,拦截规则集中。缺点是只作用于开发服务器,跑单元测试时用不了,而且配置和 Vite 强绑定,换构建工具就得重写。

我一般的取舍是:团队需要单元测试里也复用同一套 mock,选 MSW;只是单纯想让 dev server 返回假数据,选插件更省事。两者不冲突,也有项目同时用。

3.4 请求拦截方案的横向对比

方案侵入性生效范围能否复用真实请求链路上手成本适用阶段
硬编码 / 本地 JSON高(改业务代码)仅页面数据否极低页面原型期
手写 XHR / fetch 拦截无浏览器内部分中临时调试
MSW无浏览器 + Node是中全程
vite-plugin-mock无开发服务器否低开发期
抓包工具改响应无整个系统是低联调、复现
Mock 服务器无所有客户端是高多端协作

这张表建议收藏,下次团队讨论“我们用什么 mock”的时候,直接对着阶段和诉求圈一行,比争论半小时有效得多。

4. 抓包工具与代理层:不动代码的那种改法

抓包工具(各种 HTTP 调试代理)的定位跟前面完全不一样。它工作在系统的网络层,任何走这台机器的请求它都能看到,所以它不仅能改浏览器里的请求,还能改小程序开发者工具、桌面客户端的请求。当你要验证“这个接口换一批数据,页面表现是不是正常”时,它是最快的,因为不用改一行代码、不用重启服务。

常见的用法是配置一条匹配规则,匹配到指定的 URL 后返回本地文件或者手写的响应体。响应内容可以是固定的 JSON 文件,也可以用变量、时间戳、随机数做动态替换。整套流程的核心是“匹配规则 + 响应内容”两件事,看起来简单,但真正卡住人的往往是规则本身。

4.1 为什么改了响应却不生效

“改了响应数据但页面上没变化”是这类工具最高频的求助。按下面的顺序排查,通常三步之内能定位。

第一,匹配规则没命中。很多工具的匹配表达式默认是“包含”还是“正则”,语义不一样。URL 里带查询参数时,如果你只写了路径部分,可能匹配不上;反过来,如果你写了完整的带参 URL,但实际请求的参数顺序变了,也会匹配失败。最稳的做法是用正则匹配纯路径,/api/user/list这种,参数交给后面的处理逻辑。

第二,缓存和协商缓存拦截了请求。浏览器对GET请求如果命中了Cache-Control或者ETag的协商缓存,它压根不会发出真实的网络请求,抓包工具自然看不到,你改的响应也就无从生效。排查方式是打开 DevTools 的 Network 面板,看这条请求的 Size 列是不是显示(memory cache)或(disk cache),是的话勾上 Disable cache,或者在后端响应头里临时去掉缓存相关字段。

第三,HTTPS 证书没信任。HTTPS 请求要被抓包工具解密,必须让系统信任它的根证书。如果证书没装好,浏览器会直接报证书错误,请求根本走不到工具里;如果只信任了一部分(比如浏览器信任了但系统没有),就会出现“有的客户端能抓到、有的抓不到”的诡异现象。这类问题在 macOS 和 Windows 上的表现不一样,需要在系统证书管理里确认一遍。

还有一类情况是客户端自带了独立的网络实现,比如某些 App 的主进程不走系统代理,或者小程序在真机模式下有自己的证书校验逻辑,这些都会让你的规则失效。判断方法很简单:看抓包工具里这条请求到底有没有出现。没出现就是根本没走到代理,出现了但响应没变,才是规则或缓存的问题。

注意:抓包工具的能力很强,能修改别人的请求和响应,所以一定要在自己的开发设备、自己负责的测试环境里用。任何针对非自有系统的响应篡改,尤其是涉及身份校验、金额、权限判断这类字段的修改,都属于明确的越界行为,不要碰。真要做权限相关的界面调试,正确做法是在自己的开发环境里 mock 一份带权限标识的响应,或者让后端配合提供测试账号。

4.2 本地代理转发:让请求指向另一台机器

比抓包更“正规”的一种做法,是在本地起一个反向代理,把/api前缀的请求转发到另一个地址。前端项目里 Vite 和 Webpack 都内置了这个能力:

// vite.config.ts import { defineConfig } from 'vite'; export default defineConfig({ server: { proxy: { '/api': { target: 'http://127.0.0.1:3000', // 指向本地 mock 服务 changeOrigin: true, // 只在需要重写路径时使用,否则容易把真实路径改坏 // rewrite: (path) => path.replace(/^\/api/, ''), }, }, }, });

changeOrigin: true的作用是让代理请求的Host头变成目标地址,避免对方服务器因为 Host 不匹配而拒绝。而rewrite那一行我特意注释掉了,因为见过太多人复制粘贴后忘了改,结果请求路径被无声无息地改掉,接口 404 却查不出原因。

代理转发最大的价值在于跨设备调试。手机连同一个局域网,把接口地址指向你电脑的 IP,就能在真机上跑本地 mock 数据,这在做移动端适配时非常有用。相比之下,浏览器插件式的拦截只能在浏览器里生效,真机完全用不了。

5. 后端参与型 Mock:契约先行与 Mock 服务器

前面的方案都是前端自己扛,做到一定程度就会遇到瓶颈:多个端(Web、小程序、App)要共用一份数据,或者测试同学要拿固定的数据跑自动化用例。这时候就该让后端参与进来,核心思路是“契约先行”——先把接口的字段、类型、示例值定下来,任何人可以基于这份契约生成 mock 数据。

具体落地通常有两种形态。一种是把接口描述文件(如 OpenAPI/Swagger 规范)维护起来,用工具直接根据描述生成一个可访问的 Mock 服务,返回符合结构的假数据。另一种是团队自建一套 Mock 平台,界面上配置路径、方法、响应模板,发布后得到一个固定域名。两种形态的共同点是:mock 数据的唯一来源是契约,不再是某个人的本地文件。

5.1 契约驱动的好处和代价

好处非常直接:前后端在写代码之前就先对齐了字段名和类型,等真正联调的时候,最大的那类“字段名对不上”的问题被提前消灭了。而且生成的 Mock 服务返回的数据结构天然符合约定,前端写的取值逻辑换到真接口上基本不用改。

代价是维护成本。契约文件不更新,mock 服务就会一直返回过期结构,反而比没有更糟——因为大家会默认它是准的。所以必须有人负责,通常是后端主 R 或者架构同学,在每个接口评审后同步更新。我见过做得好的团队,把契约更新纳入了 CI,接口代码和契约不一致时流水线直接失败,虽然前期麻烦,但长期看省下的联调时间非常可观。

5.2 数据不一致的几种典型形态

配合契约使用时,下面这几种不一致最容易出现,值得单独列出来对照检查。

不一致类型典型表现排查方式预防手段
命名风格前端取userId得到 undefined打印完整响应体,核对字段名契约里明确命名规范
空值语义无数据时返回 null,前端按数组处理报错构造空结果集,看是否白屏约定空集合统一返回[]
数值类型金额 mock 是 number,真实是 string用typeof打印类型契约里标注类型和精度
时间格式mock 是时间戳,真实是 ISO 字符串对比格式化函数的输入统一用时间戳或统一字符串
枚举取值mock 里 status 是 0/1,真实是字符串穷举枚举值契约里附枚举字典
层级嵌套mock 少了一层data逐层打印取值路径契约里写完整示例

这张表的用法很简单:联调出问题时,从上往下逐行排除,通常第二三行就能命中。我习惯在项目的docs/里放一份,新人入职时先看这个,比自己摸索快很多。

5.3 微前端场景下的一个额外注意点

项目里用了微前端方案时,主应用和子应用各自可能有自己的 mock 配置。子应用用 MSW 起了 Service Worker,主应用也在拦截请求,两边规则一重叠,就会互相覆盖,表现为“子应用接口返回了主应用的数据”。这种情况下,把拦截全部收敛到主应用一层,子应用只负责提供 handler 配置,由主应用统一注册;或者约定好各自只拦截自己前缀的路径,比如子应用只处理/api/order/*。这个坑不常见,但一旦踩上很难定位,因为它表现出的现象跟业务逻辑毫无关系。

6. 工程化落地:把 Mock 开关当成一个功能来做

方案选完之后,真正决定这套东西好不好用的是工程化细节。前面反复提到“开关”,这里展开说一下。核心原则只有一条:mock 必须是一个可以明确开启、明确关闭、默认关闭的状态,而不是散落在代码里的隐式行为。

6.1 环境变量与目录约定

推荐的目录结构大概是这样的:

src/ mocks/ handlers.ts // 所有请求规则,按业务分文件后在这里汇总 browser.ts // 浏览器端启动逻辑 server.ts // Node 端启动逻辑,供单元测试使用 generator/ // 数据生成器,带固定种子的伪随机 user.ts order.ts data/ // 静态 JSON,字典类数据放这里 dict.json

环境变量只留一个VITE_USE_MOCK,true时启动,其余情况一律不启动。这样在预发和生产环境,无论谁误提交了什么,只要构建脚本没带这个变量,mock 就永远不会生效。比起在代码里写if (process.env.NODE_ENV === 'development')这种判断,环境变量的可控性更强,因为它是构建时注入的,浏览器端改不了。

单元测试里复用同一套 handler 也很简单:

// tests/setup.ts import { setupServer } from 'msw/node'; import { handlers } from '../src/mocks/handlers'; export const server = setupServer(...handlers); beforeAll(() => server.listen()); afterEach(() => server.resetHandlers()); afterAll(() => server.close());

resetHandlers这一句不能省。有的用例为了测异常分支临时覆盖了某条规则,如果不重置,后面的用例会莫名其妙地继承上一个用例的行为,出现“单独跑通过、一起跑失败”的经典问题。

6.2 数据防丢失:mock 数据的版本管理

mock 数据要不要进版本库,这个问题我犹豫过很久,最后得到的结论是:生成器和字典进,大规模静态数据文件不进。生成器是代码,逻辑变了需要 review;字典是业务知识,比如城市列表、状态枚举,属于团队资产。而那种几百行的列表数据文件,本质上是把数据库导出到了仓库里,改动频繁、体积大、还容易夹带不该出现的测试数据,直接加进.gitignore,需要的人自己生成。

如果确实需要一份固定的、可复现的数据集,用前面提到的固定种子生成器来产出,把种子值写进代码注释里,谁都能重新算出同一批数据。这比提交一个巨大的 JSON 文件干净得多,也避免了误提交真实数据带来的风险。

注意:任何 mock 数据里都不要出现真实的手机号、身份证号、银行卡号、内部人员姓名。造数据时统一用明显假的格式,比如昵称用“测试用户A”,手机号用13800000000这类保留号段。这件事在数据合规上没有任何商量余地。

7. 常见问题速查与踩坑实录

把踩过的坑整理成表,出问题的时候按行对号入座,比翻文档快。

7.1 问题速查表

现象最可能的原因快速验证方式处理办法
handler 配了但请求还是打到后端Service Worker 没注册成功,或路径前缀不匹配Network 面板看请求来源,控制台看 SW 状态检查msw init生成的文件是否可访问,匹配规则加通配
页面一直 loading 不结束拦截后没触发响应事件看 Promise 是否一直 pending手写拦截时手动派发load事件
改了响应没生效被浏览器缓存命中Network 面板看 Size 列是否为 cache勾选 Disable cache,临时去掉缓存响应头
抓包工具看不到请求未安装信任证书,或客户端不走系统代理工具里完全没有这条记录安装并信任根证书,确认客户端代理设置
第一页数据为空分页下标算错打印start和size用(page - 1) * size
每次刷新数据都变用了真随机连续刷新对比改用固定种子的伪随机
单元测试单独过、一起跑失败handler 被上一个用例污染调整用例顺序复现每个用例后resetHandlers
主应用和子应用数据串了两层拦截互相覆盖看返回的数据属于哪个模块收敛到单层拦截,或按路径前缀隔离
线上包里出现假数据静态 import 被打包搜索构建产物里的 mock 关键字mock 数据改为动态 import,加构建期校验

7.2 几个我反复用到的实操心得

心得一:先造假数据,再写真组件。很多人的顺序是先写页面再补数据,结果是页面写完了,发现数据形状跟接口对不上,返工。我的习惯是先把一条真实响应拷贝出来,脱敏后存成样本,然后照着样本写组件。这样即使后面接口改了字段,改的也只是取值路径,DOM 结构不用动。

心得二:给每个 mock 规则加注释说明来源。写清楚“这条规则对齐的是 xx 接口 v2 版本”,别人接手时知道该找谁确认。半年后你自己回来看,也会感谢当时的自己。

心得三:延迟不要设成 0。网络延迟设为 0 的后果是 loading 状态一闪而过,你根本看不出加载动画有没有问题、骨架屏有没有生效。设个 200~500 毫秒,既能看清过渡,又不至于烦人。需要专门测弱网时,单独加一条延迟 3000 毫秒的规则,用完删掉。

心得四:主动造失败场景。真实项目里最容易被忽略的就是错误分支。我通常在 handlers 里给每个接口都配一条可通过开关触发的失败规则,切换成本极低,却能提前发现一堆“接口挂了白屏”的问题。业务代码里那句catch有没有写好,只有真出错的时候才知道。

心得五:交付前跑一遍真实数据。不管 mock 做得多完美,交付前一定连一次真实接口,重点看三个地方:字段名、空值形态、时间格式。我见过太多“本地全对、联调全崩”的情况,根因都是 mock 数据太理想化了——真实的接口不会每次都返回你期望的那个形状,它会返回 null、返回空字符串、返回超出你预期的长度。提前用真实数据压一遍,比事后救火划算得多。

心得六:别把 mock 当成长期方案。它的价值在于让开发不停摆,而不是替代后端。当一个接口的 mock 规则改了三次以上,说明契约本身不稳定,这时候该做的是拉着后端把字段定下来,而不是继续在前端维护一份越来越复杂的假数据。我个人判断的标准是:mock 规则的生命周期超过两周还没对齐,就该升级成契约同步的问题了。

最后再分享一个小技巧。如果项目里 mock 规则比较多,可以在 handler 里加一个统一的前置日志,把匹配到的路径、方法、查询参数打出来。这样当某个接口行为异常时,你一眼就能看出请求到底有没有被拦截、参数是什么形状,比在浏览器里一层层翻 Network 面板快得多。这套日志在开关关闭后不会被加载,也不会有任何线上开销。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询