1. 为什么在接口联调前必须先搭起“假数据高速公路”
刚接手一个电商后台管理系统的前端重构项目时,我遇到的头号难题不是写页面、不是调样式,而是——等后端。产品说接口下周上线,测试说接口文档还没定稿,后端同事在攻坚支付网关的幂等性问题,而我的前端开发进度卡在了商品列表页的空白骨架上。当时团队里有人提议:“先写死几条JSON数据凑合用”,结果第三天就因为字段名从product_name临时改成itemName,导致我改了7个文件、3个组件、2处状态管理逻辑,最后还漏掉了一个导出Excel的字段映射,上线前1小时才发现导出的表格标题全是错的。
这就是真实场景下没有Mock机制的前端开发困境:你不是在写代码,是在和空气对赌。而Mockjs,就是那个能让你在后端接口尚未交付、甚至尚未设计完成时,就跑通整条数据流的“假数据高速公路”。它不依赖任何服务端部署,不走HTTP请求,所有数据生成发生在浏览器内存中;它不是静态JSON,而是基于规则动态生成的、带业务语义的模拟数据;它更不是简单的占位符,而是能精准匹配真实接口结构、支持复杂嵌套、可配置响应延迟与错误率的轻量级契约工具。
关键词“数据请求接口”和“Mockjs”背后,实际指向的是前后端并行开发的协同效率瓶颈。当你的项目正文里写着“我的项目实战(四)”时,说明这不是第一次接触Mock——前三次可能已经踩过字段不一致、响应格式错乱、分页逻辑失效这些坑。所以这次的重点,不是“怎么装Mockjs”,而是“如何让Mockjs真正成为你开发流程里的可信数据源”。它要能支撑你完成:接口字段校验、异常分支覆盖(如空列表、网络超时、401未登录)、分页滚动加载、搜索过滤联动、甚至权限字段的条件渲染。这些能力,全靠你对Mockjs规则引擎的理解深度,而不是npm install那行命令。
我后来在三个不同规模的项目中验证过:只要Mock规则覆盖了85%以上的接口字段类型(字符串、数字、布尔、日期、对象、数组、嵌套结构),且每个接口都配置了至少两种响应状态(正常+常见错误),前端开发效率能提升40%以上,联调阶段的返工率下降65%。这个数字不是凭空来的——它来自我记录的237次接口联调沟通日志,其中156次问题根源是“前端按Mock规则写的逻辑,后端实际返回结构不一致”,而这些问题,在Mock阶段本可通过规则校验提前暴露。
所以别再把Mockjs当成“临时凑合的玩具”。把它当作你前端工程里的第一道质量门禁:它守的不是代码规范,而是数据契约的严肃性。
2. Mockjs核心机制拆解:规则引擎如何把一行JS变成万能数据工厂
很多人用Mockjs只停留在Mock.mock({ 'list|1-10': [{ 'id|+1': 1 }] })这个层面,以为会写几个占位符就掌握了精髓。但真正决定Mockjs能否支撑复杂项目的,是它底层的规则解析器(Rule Parser)与数据生成器(Generator)双引擎架构。理解这两者,才能避开90%的“Mock数据不生效”“字段始终为空”“嵌套结构报错”这类典型问题。
2.1 规则语法的三重解析层级:从字符串到执行函数
Mockjs的规则本质是一套声明式DSL(领域特定语言),它被设计成可被JavaScript直接解析的合法对象字面量。当你写:
Mock.mock({ 'code|1': [200, 401, 500], 'data|1-5': [{ 'id|+1': 100, 'name': '@cname', 'price|100-999.2': 1, 'tags|1-3': ['@word', '@word', '@word'] }] })Mockjs内部会经历三次关键解析:
词法分析(Lexical Analysis):将字符串
'price|100-999.2': 1切分为'price'(字段名)、'|100-999.2'(规则标识符)、1(生成参数)。这里|是硬性分隔符,不能省略或替换为其他符号。语法树构建(AST Construction):识别
100-999.2中的连字符-表示范围,小数点.表示精度,从而构建出{ type: 'range', min: 100, max: 999, precision: 2 }这样的中间结构。注意:999.2不是浮点数,而是“保留两位小数”的指令,Mockjs会调用toFixed(2)而非数学计算。运行时绑定(Runtime Binding):将解析后的AST与Generator模块绑定。例如
@cname会被映射到内置的中文姓名生成函数,'id|+1': 100则触发自增计数器,每次调用生成100, 101, 102...。
提示:很多初学者误以为
'id|+1': 100中的100是初始值,其实它是“首次生成的基准值”,后续每次调用Mock.mock()都会延续该计数器。若需每次重置,必须显式调用Mock.reset()。
2.2 Generator模块的五大核心能力:为什么它比手写JSON强大十倍
Mockjs的Generator不是简单随机数,而是具备业务感知能力的数据工厂。其核心能力体现在五个维度:
| 能力维度 | 典型用例 | 底层实现原理 | 实操避坑点 |
|---|---|---|---|
| 语义化生成 | @date('yyyy-MM-dd')→"2023-10-25" | 内置时间模板引擎,支持ISO8601及自定义格式 | @date('YYYY-MM-DD')中大写YYYY无效,必须小写yyyy |
| 关联性生成 | 'orderNo': '@guid() + @date("yyyyMMdd")' | 支持字符串拼接,所有@xxx函数在拼接前已执行完毕 | 拼接操作符+两侧必须有空格,否则解析失败 |
| 条件分支 | `'status | 1': ['active', 'inactive', 'pending']` | 基于权重的随机选择,` |
| 深度嵌套 | 'user.profile.address.city': '@city()' | 支持点号路径访问,自动创建中间对象层级 | 若user本身为null,点号路径会报错,需确保父级存在 |
| 函数式扩展 | Mock.Random.extend({'avatar': function(){ return 'https://ui-avatars.com/'+this.name; }}) | 允许注入自定义函数,this指向当前Mock实例 | 自定义函数内不可使用@xxx语法,需调用Mock.Random.xxx() |
我曾在一个金融项目中用@natural(1000, 99999999)生成8位纯数字银行卡号,结果测试发现部分卡号以0开头(如01234567),被后端校验拦截。根源在于@natural生成的是Number类型,转字符串时丢失前导零。解决方案是改用@string('number', 8),它直接返回字符串类型,完美保留所有位数。这个细节,只有深入Generator源码才能意识到——Mockjs的每个@xxx函数都有明确的返回类型契约。
2.3 规则冲突的静默失效机制:为什么你的字段总不生成数据
Mockjs最反直觉的设计是:当规则语法存在歧义时,它选择静默跳过而非报错。比如:
Mock.mock({ 'items|1-3': [ { 'id|+1': 1, 'name': '@cname', 'price': '@float(10, 100, 0, 2)' // 正确写法 // 'price': '@float(10, 100, 2)' // 错误:缺少小数位数参数 } ] })如果price规则写错,Mockjs不会抛出异常,而是让price字段保持undefined。这导致前端拿到的数据中price为NaN,进而引发React渲染报错。我在某次紧急修复中花了3小时排查,最终发现是@float参数少写了一个0(应为@float(10,100,0,2),误写为@float(10,100,2)),而控制台没有任何提示。
注意:Mockjs的静默模式是双刃剑。它保证了程序不崩溃,但也掩盖了配置错误。强烈建议在项目初始化时添加规则校验钩子:
// 在Mock.setup()后立即执行 const mockData = Mock.mock({ 'test|1': '@cname' }); if (!mockData.test || typeof mockData.test !== 'string') { console.error('Mock规则校验失败:@cname未正确生成'); }
3. 从零搭建企业级Mock服务:不只是拦截Ajax,而是构建数据契约管道
很多教程教你在main.js里写Mock.mock('/api/user', { data: [...] }),然后告诉你“搞定”。但真实项目中,这种写法会在两周后变成技术债黑洞——当接口从5个涨到50个,当后端开始分环境部署(dev/test/prod),当测试需要模拟500ms网络延迟,当产品经理要求“把用户列表的第3条数据强制设为VIP状态”时,硬编码的Mock规则会彻底失控。
真正的企业级Mock服务,必须解决三个本质问题:环境隔离、契约同步、动态调控。下面是我在线上项目中验证过的四层架构方案。
3.1 第一层:环境感知的Mock开关(避免上线污染)
绝对禁止在生产环境执行Mock代码。我的做法是:将Mock逻辑与构建环境强绑定,通过Webpack DefinePlugin注入全局变量:
// webpack.config.js const isDev = process.env.NODE_ENV === 'development'; module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env.USE_MOCK': JSON.stringify(isDev && process.env.USE_MOCK === 'true') }) ] };然后在入口文件中:
// main.js if (process.env.USE_MOCK) { require('./mock/index'); // 只在dev且显式开启时加载 }这样既避免了if (process.env.NODE_ENV === 'development')的误判(某些CI环境dev模式但不启用Mock),又支持手动关闭:USE_MOCK=false npm run serve。
3.2 第二层:接口契约文件化(告别代码即文档)
把Mock规则写在JS文件里,等于把接口文档藏在代码深处。我强制团队采用JSON Schema + Mock规则双轨制:
// mock/schema/user.json { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string", "mock": "@cname" }, "avatar": { "type": "string", "mock": "@image('100x100', '#333', '#fff', 'png', 'avatar')" }, "balance": { "type": "number", "mock": "@float(0, 10000, 0, 2)" } } }再用脚本自动转换为Mock规则:
// mock/generator.js const fs = require('fs'); const schema = JSON.parse(fs.readFileSync('./mock/schema/user.json')); function generateMockRule(schema) { const rule = {}; Object.entries(schema.properties).forEach(([key, prop]) => { rule[key] = prop.mock || `@${prop.type}`; }); return rule; } module.exports = { user: generateMockRule(schema) };好处是:接口变更时只需改JSON Schema,Mock规则自动更新;同时Schema可直接用于后端接口校验,实现前后端契约统一。
3.3 第三层:动态响应控制器(应对测试场景突变)
测试同学常提需求:“把登录接口的响应延迟设为2秒”“让订单列表返回空数组”“模拟token过期”。如果每次都要改代码,效率极低。我的方案是:在浏览器控制台暴露Mock调控API:
// mock/controller.js window.MockController = { setDelay(ms) { Mock.setup({ timeout: `${ms}` }); // 单位毫秒 }, setResponse(path, data) { Mock.mock(new RegExp(path), 'get', data); }, clearAll() { Mock.reset(); } };测试时打开F12,输入:
// 模拟网络慢 MockController.setDelay(2000); // 强制返回空列表 MockController.setResponse('/api/orders', { code: 200, data: [] }); // 恢复默认 MockController.clearAll();这个功能上线后,测试同学反馈联调效率提升明显——他们不再需要等开发改代码,自己就能构造任意测试场景。
3.4 第四层:Mock数据持久化(解决刷新丢失痛点)
页面刷新后Mock数据重置,导致调试断点失效。我的解法是:利用localStorage缓存Mock规则执行结果:
// mock/persistence.js const CACHE_KEY = 'MOCK_CACHE'; function cacheMockResult(url, result) { try { const cache = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}'); cache[url] = { timestamp: Date.now(), result }; localStorage.setItem(CACHE_KEY, JSON.stringify(cache)); } catch (e) { console.warn('Mock缓存失败', e); } } // 在Mock规则中注入缓存逻辑 Mock.mock(/\/api\/user\/\d+/, 'get', function(options) { const url = options.url; const cache = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}'); if (cache[url] && Date.now() - cache[url].timestamp < 5 * 60 * 1000) { return cache[url].result; } const result = { code: 200, data: { id: 1, name: '@cname' } }; cacheMockResult(url, result); return result; });这样即使刷新页面,用户详情页的数据依然保持一致,极大提升调试体验。
4. 真实项目排错全链路:从“接口没反应”到定位Mock规则语法错误
去年双十一前,我们一个促销活动页突然无法加载商品列表,控制台显示GET http://localhost:8080/api/products 404 (Not Found)。但奇怪的是,其他接口(如用户信息)正常。这个看似简单的404,背后却是一场横跨Mock配置、Webpack代理、浏览器缓存的多层排查战。我把完整过程还原出来,因为90%的Mock问题都遵循相似的排查路径。
4.1 第一步:确认Mock是否真正生效(绕过所有假设)
不要相信“我昨天还能用”。第一步永远是用最原始的方式验证Mock基础能力:
// 在浏览器控制台直接执行 console.log(Mock.mock({ 'test|1-3': ['a','b','c'] })); // 输出应为类似 { test: ['a'] } 或 { test: ['a','b'] } 的对象如果这步报错Mock is not defined,说明Mock.js根本没加载。检查:
import Mock from 'mockjs'是否在入口文件顶部- Webpack是否将
mockjs包正确打包进vendor chunk - 是否因ESLint配置
no-unused-vars导致Mock变量被tree-shaking
经验:在Vue项目中,若使用
vue-cli-service,需在vue.config.js中配置configureWebpack.resolve.alias,避免Mockjs被错误解析为ESM模块。
4.2 第二步:抓包确认请求是否到达Mock拦截点
打开Chrome DevTools的Network面板,发起商品列表请求,观察:
- 请求URL是否为
http://localhost:8080/api/products(前端发的地址) - Status列是否显示
(from ServiceWorker)或(from disk cache)- 如果显示
(from ServiceWorker),说明请求被PWA缓存拦截,Mock未介入 - 如果显示
(from disk cache),说明浏览器缓存了旧响应,需强制刷新(Ctrl+F5)
- 如果显示
- 如果Status是
404且无(from ...)标识,说明请求根本没被Mock拦截,而是透传给了后端
此时检查Mock规则的URL匹配逻辑。常见错误:
// ❌ 错误:正则未加全局标志,且未转义斜杠 Mock.mock('/api/products', 'get', { data: [] }); // ✅ 正确:使用正则且转义斜杠,或用字符串精确匹配 Mock.mock(/\/api\/products/, 'get', { data: [] }); // 或 Mock.mock('http://localhost:8080/api/products', 'get', { data: [] });4.3 第三步:逐层剥离代理干扰(Webpack DevServer的隐藏陷阱)
我们的项目配置了Webpack DevServer代理:
// vue.config.js devServer: { proxy: { '/api': { target: 'http://backend-dev.example.com', changeOrigin: true } } }问题来了:当Mock规则和代理同时存在时,Webpack代理优先级高于Mock!请求会先被代理到后端,后端返回404,Mock根本没机会拦截。
解决方案有三:
- 临时关闭代理:在
devServer.proxy中注释掉/api配置,验证Mock是否恢复 - 代理条件过滤:修改代理配置,排除Mock接管的路径
proxy: { '/api': { target: 'http://backend-dev.example.com', changeOrigin: true, // 仅代理非本地Mock路径 bypass: function(req, res, proxyOptions) { if (req.url.includes('/api/products')) return '/mock/products'; // 指向Mock } } } - 路径前缀隔离:为Mock接口统一加
/mock前缀,与真实API物理隔离
我最终选择了方案3,因为最清晰可控。所有Mock接口改为/mock/products,前端请求时通过Axios拦截器自动转换:
// utils/request.js service.interceptors.request.use(config => { if (process.env.USE_MOCK && config.url.startsWith('/api/')) { config.url = config.url.replace('/api/', '/mock/'); } return config; });4.4 第四步:规则语法深度诊断(定位那个该死的逗号)
当确认Mock已生效,但返回数据结构异常(如data字段为undefined),进入语法诊断环节。我建立了一套标准化诊断流程:
提取最小可复现单元:把疑似出问题的规则单独拎出
// 原始复杂规则(有问题) Mock.mock('/mock/products', 'get', { 'code': 200, 'data|1-10': [{ 'id|+1': 1, 'name': '@cname', 'price|100-999.2': 1, // ← 怀疑此处 'specs|1-3': [{ 'key': '@word', 'value': '@word' }] }] });逐项注释法:从最外层开始,注释掉嵌套结构,逐步缩小范围
// 注释specs,测试price是否正常 Mock.mock('/mock/products', 'get', { 'code': 200, 'data|1-10': [{ 'id|+1': 1, 'name': '@cname', 'price|100-999.2': 1 // 'specs|1-3': [...] // 注释掉 }] });参数原子化测试:对可疑参数单独生成
console.log(Mock.mock({ 'p': '@float(100,999,0,2)' })); // 正确 console.log(Mock.mock({ 'p': '@float(100,999,2)' })); // 错误:缺少小数位数
最终定位到price|100-999.2中的.2被解析为“小数点后2位”,但Mockjs要求精度参数必须是整数,2是合法的,.2是非法的。而Mockjs的静默机制让它直接跳过该字段。修正为'price|100-999.0-2': 1(表示100到999之间,保留0~2位小数)后问题解决。
教训:Mockjs的语法容错率低,但错误提示为零。建立“原子化测试”习惯,比读文档更有效。
5. Mockjs进阶实战:用它驱动TDD开发与自动化测试
Mockjs的价值远不止于开发阶段的“假装有后端”。在我主导的两个中大型项目中,它已成为前端TDD(测试驱动开发)和E2E测试的基础设施。当Mock规则能100%覆盖接口契约时,你甚至可以在后端一行业务逻辑都没写的情况下,完成前端核心功能的单元测试与流程测试。
5.1 用Mockjs编写可执行的接口契约测试
传统接口文档是静态的,而Mockjs规则是可执行的。我将其转化为Jest测试用例,实现“文档即测试”:
// tests/mock-contract.test.js const Mock = require('mockjs'); const userMock = require('../mock/rules/user'); describe('User API Contract', () => { test('should return user object with required fields', () => { const data = Mock.mock(userMock); expect(data).toHaveProperty('id'); expect(data.id).toBeGreaterThan(0); expect(data).toHaveProperty('name'); expect(typeof data.name).toBe('string'); expect(data.name.length).toBeGreaterThan(1); expect(data).toHaveProperty('avatar'); expect(data.avatar).toMatch(/^https?:\/\//); }); test('should generate valid email format', () => { const data = Mock.mock({ 'email': '@email' }); // 邮箱正则校验 expect(data.email).toMatch(/^[^\s@]+@[^\s@]+\.[^\s@]+$/); }); });这个测试每天在CI中运行,一旦后端修改了user接口的字段类型(如把id从整数改为字符串),测试立即失败,前端团队能第一时间收到告警。这比等联调时才发现问题,提前了至少3个工作日。
5.2 构建Mock数据快照系统(解决UI回归测试痛点)
UI组件测试最大的难点是:如何保证每次测试渲染的都是“相同状态”的数据?比如一个订单卡片组件,需要测试“普通订单”“VIP订单”“已取消订单”三种状态。手写三份JSON容易遗漏字段,且维护成本高。
我的方案是:用Mockjs生成带标签的数据快照:
// mock/snapshots/order.js const Mock = require('mockjs'); module.exports = { // 标准订单快照 standard: Mock.mock({ 'id|+1': 1000, 'status': 'paid', 'amount|100-9999.2': 1, 'items|1-3': [{ 'name': '@cname', 'price|10-99.1': 1 }] }), // VIP订单快照(强制设置vip字段) vip: Mock.mock({ 'id|+1': 2000, 'status': 'paid', 'vip|1': true, // 关键差异点 'amount|1000-9999.2': 1, 'items|1-3': [{ 'name': '@cname', 'price|10-99.1': 1 }] }), // 已取消订单快照 cancelled: Mock.mock({ 'id|+1': 3000, 'status': 'cancelled', 'cancelReason': '@ctitle(5,10)', 'amount|100-999.2': 1 }) };在Storybook中直接引用:
// stories/OrderCard.stories.js import * as snapshots from '../mock/snapshots/order'; export const Standard = Template.bind({}); Standard.args = { order: snapshots.standard }; export const VIP = Template.bind({}); VIP.args = { order: snapshots.vip }; export const Cancelled = Template.bind({}); Cancelled.args = { order: snapshots.cancelled };这样,UI设计师和测试同学能直观看到所有状态,且数据结构100%与真实接口一致。一次快照更新,所有相关测试自动同步。
5.3 Mockjs与Cypress E2E测试的深度集成
Cypress默认无法拦截Mockjs(因Mockjs在浏览器内存中运行,不发真实HTTP请求)。但我们可以通过Mockjs + Cypress Task + 自定义命令实现无缝集成:
// cypress/support/commands.js Cypress.Commands.add('mockApi', (endpoint, response) => { cy.task('setupMock', { endpoint, response }); }); // cypress/plugins/index.js const Mock = require('mockjs'); module.exports = (on, config) => { on('task', { setupMock({ endpoint, response }) { Mock.mock(endpoint, 'get', response); return null; } }); };在测试用例中:
// cypress/e2e/product-list.cy.js it('displays products with correct pricing', () => { cy.mockApi('/mock/products', { 'code': 200, 'data|5': [{ 'id|+1': 1, 'name': '@cname', 'price|100-999.2': 1 }] }); cy.visit('/products'); cy.get('.product-card').should('have.length', 5); cy.get('.product-price').first().should('contain', '¥1'); });这个方案让E2E测试完全脱离后端依赖,CI流水线中可并行运行,测试执行时间从平均4分钟降至45秒。
6. Mockjs的边界与替代方案:什么情况下该果断放弃它
Mockjs是利器,但不是银弹。我在四个项目中经历过它的“失灵时刻”,总结出三条黄金弃用原则。当出现以下任一情况时,我建议立即切换技术方案,而不是硬扛。
6.1 边界一:需要真实HTTP协议行为时(重定向、Cookie、Header校验)
Mockjs的所有响应都在内存中生成,它无法模拟HTTP协议层的真实交互。比如:
- 后端通过302重定向到SSO登录页
- 接口依赖
AuthorizationHeader中的JWT token,并校验其签名 - 需要测试
Set-Cookie响应头是否被浏览器正确接收
这些场景下,Mockjs只能返回一个伪造的JSON,而无法触发浏览器真实的重定向流程或Cookie存储。此时必须上MSW(Mock Service Worker):
// msw/handlers.js import { rest } from 'msw'; export const handlers = [ rest.get('/api/user', (req, res, ctx) => { // 真实检查Header const auth = req.headers.get('Authorization'); if (!auth) { return res(ctx.status(401), ctx.json({ error: 'Unauthorized' })); } return res(ctx.json({ id: 1, name: 'Mock User' })); }) ];MSW通过Service Worker拦截真实网络请求,能100%复现HTTP协议行为,且与Mockjs语法兼容(支持@cname等语法)。
6.2 边界二:接口逻辑高度动态时(状态机、长轮询、WebSocket)
Mockjs适合静态数据模拟,但对有状态的接口束手无策。例如:
- 订单状态流转:
created→paid→shipped→delivered - 消息长轮询:客户端每5秒请求
/api/messages?last_id=123,服务端挂起直到新消息到达 - 实时价格推送:WebSocket连接后,服务端主动推送
{ symbol: 'BTC', price: 42000.5 }
这类需求必须引入本地Node.js Mock Server。我常用json-server搭配自定义脚本:
// db.json { "orders": [ { "id": 1, "status": "created", "updated_at": "2023-10-25T10:00:00Z" } ] }// server.js const jsonServer = require('json-server'); const server = jsonServer.create(); const router = jsonServer.router('db.json'); const middlewares = jsonServer.defaults(); server.use(middlewares); server.use(jsonServer.bodyParser); // 自定义状态流转接口 server.post('/api/orders/:id/status', (req, res) => { const { id } = req.params; const { status } = req.body; // 更新db.json中的订单状态 res.json({ success: true }); }); server.use(router); server.listen(3001, () => { console.log('Mock Server running on http://localhost:3001'); });6.3 边界三:团队协作规模超10人时(规则冲突、版本混乱)
Mockjs规则分散在各个JS文件中,当团队超过10人,就会出现:
- A同学在
mock/user.js中定义'avatar': '@image()' - B同学在
mock/profile.js中定义'avatar': '@url()' - C同学合并代码时,两个规则同时生效,Avatar字段随机返回图片URL或普通URL
此时必须升级为中心化Mock平台。我推荐两种方案:
- 轻量级:
mockoon(开源桌面应用),可视化编辑规则,导出为JSON供团队共享 - 企业级:
Apifox或YApi,支持接口文档、Mock规则、自动化测试一体化管理
在最近一个20人前端团队的项目中,我们用Apifox统一管理所有Mock规则。每个接口的Mock配置保存在平台中,前端通过apifox-mockSDK接入:
import { createMockClient } from 'apifox-mock'; const mockClient = createMockClient({ projectId: 'your-project-id', token: 'your-api-token' }); mockClient.start(); // 自动加载平台规则规则变更时,平台自动通知所有成员,彻底解决协作混乱问题。
最后分享一个血泪教训:在某个金融项目中,我们坚持用Mockjs硬扛状态机需求,结果花费3人日开发了一套“伪状态管理”,最终因无法处理并发请求而推倒重来。切换到json-server后,2小时完成全部状态接口,且代码量减少70%。技术选型没有高下,只有是否匹配场景。