news 2026/10/21 12:42:49

前端接口模拟实战指南:从手动拦截到TypeScript驱动的工程化Mock

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端接口模拟实战指南:从手动拦截到TypeScript驱动的工程化Mock

1. 为什么前端必须“假装”有后端?——从联调现场的真实窒息感说起

我第一次在某跨平台系统项目里被拉进联调群时,后端同事发来一句:“接口文档已更新,明天下午提测”。我点点头,打开 Postman 粘贴 URL,敲下回车——页面上赫然跳出503 Service Unavailable。再刷新,404 Not Found。第三次,干脆连域名都解析失败。群里没人说话。十分钟后,后端发来新消息:“数据库迁移中,API 服务暂停 2 小时”。我盯着自己刚写完的 37 行 React 组件代码,里面 5 处fetch('/api/user/profile')全部挂起,loading 状态卡死,错误边界没兜住,整个用户中心页面白屏。

这就是前端日常里最真实的断点:后端进度不可控,但前端开发不能停;接口未就绪,但 UI 要跑通、逻辑要验证、交互要测试、提测节点雷打不动。你不能等,但又不能硬写if (env === 'dev') return mockData这种散落在各处的临时补丁——它会在某次上线前夜被遗忘,悄悄把 mock 数据塞进生产环境;它会让团队新人花两小时搞不清“这个 user.id 是真实返回的还是我写的假数据”;它更会让 E2E 测试脚本在 CI 环境里反复失败,只因mockData里漏写了avatarUrl字段,而真实接口恰好新增了该字段校验。

所以,“前端模拟后端接口返回数据”从来不是什么“锦上添花的技巧”,而是现代前端工程化链条里一道强制性的承重墙。它解决的不是“能不能跑起来”的问题,而是“能不能稳、能不能测、能不能协同、能不能提前暴露问题”的系统性瓶颈。关键词就三个:可控、可测、可协同。可控,指前端对数据流拥有完全主权,不依赖外部服务状态;可测,指能构造边界条件(空数组、超长字符串、null 值、网络延迟)验证组件健壮性;可协同,指设计稿评审阶段就能用真实数据结构跑通交互,UI/UX/前端三方基于同一份“协议”对齐预期,而不是等后端交付后再推翻重做。

这背后其实是一场静默的范式迁移:从前端视角看,接口不再是“远程调用”,而是“契约定义”。我们真正需要模拟的,从来不是某个 URL 的响应体,而是UserDetailResponse这个 TypeScript 接口所描述的数据契约。只要契约不变,前端就能独立演进。这也是为什么如今主流方案都绕不开类型驱动——Mock 数据不是凭空捏造,而是对interface的具象化填充。当你在 VS Code 里输入user.,智能提示能准确列出name,email,roles[],那说明你的 mock 不仅能跑,还和真实后端在类型层面完成了对齐。这才是“模拟”的终极目标:让前端开发体验,无限逼近于后端已就绪的理想状态。

2. 四类模拟方案的实战剖解:从浏览器控制台到工程化流水线

面对“模拟接口”这个需求,不同阶段、不同规模、不同协作模式的项目,会自然演化出截然不同的技术路径。没有银弹,只有适配。我参与过的 8 个中大型项目里,方案选择几乎严格遵循一个隐性公式:团队成熟度 × 接口复杂度 × 协作颗粒度 = 最优方案类型。下面按落地复杂度从低到高,拆解四类真实场景中高频出现的方案,每一种我都亲手踩过坑、改过源码、压过测。

2.1 方案一:浏览器控制台直写fetch拦截(适合单人调试、5 分钟救急)

这是所有前端人的“本能反应”。打开 DevTools → Console,粘贴一段 JS:

// ⚠️ 仅限本地调试,切勿提交! const originalFetch = window.fetch; window.fetch = async function(url, options) { if (url.includes('/api/user/profile')) { return Promise.resolve({ ok: true, status: 200, json: () => Promise.resolve({ id: 123, name: "张三", email: "zhangsan@example.com", avatarUrl: "https://example.com/avatar.png" }) }); } return originalFetch(url, options); };

它有效,但危险得像在刀尖上跳舞。问题在于:它污染全局 fetch,且无法处理请求头、method、body 等关键参数。当后端接口要求Content-Type: application/json并校验X-Auth-Token时,这段代码直接失效。更致命的是,它无法与任何构建工具集成——Webpack/Vite 的 HMR(热模块替换)会反复执行这段代码,导致拦截逻辑被叠加注册,一次请求触发三次 mock 返回。我在某电商后台项目里就因此遇到过商品列表重复渲染 3 遍的诡异 Bug,排查了整整半天才定位到是控制台残留的 mock 脚本。

提示:此方案唯一安全的使用姿势是——在控制台执行后,立刻在 Network 面板里勾选 “Disable cache”,然后手动刷新页面。切记刷新后不要再次执行,否则拦截器会指数级叠加。

2.2 方案二:MSW(Mock Service Worker)——当前工程化项目的事实标准

如果说方案一是手电筒,MSW 就是探照灯。它通过 Service Worker 在浏览器与网络层之间建立一个“中间代理”,所有fetch/XMLHttpRequest请求都会先经过它。关键优势在于:它完全复刻真实网络行为——支持 GET/POST/PUT/DELETE,能读取并匹配请求头、URL 参数、请求体(JSON/form-data),甚至能模拟网络延迟、随机失败率。更重要的是,它与构建工具深度集成,Vite/Webpack 插件能自动注入 SW 脚本,且支持 Node.js 环境运行(用于 Jest 单元测试)。

一个典型配置长这样(mocks/handlers.js):

import { rest } from 'msw'; export const handlers = [ // 精确匹配 GET /api/users?role=admin rest.get('/api/users', (req, res, ctx) => { const role = req.url.searchParams.get('role'); if (role === 'admin') { return res( ctx.status(200), ctx.delay(300), // 模拟 300ms 网络延迟 ctx.json([ { id: 1, name: '管理员A', role: 'admin' }, { id: 2, name: '管理员B', role: 'admin' } ]) ); } return res(ctx.status(400), ctx.json({ error: 'Invalid role' })); }), // 匹配 POST /api/login,校验请求体 rest.post('/api/login', async (req, res, ctx) => { const { username, password } = await req.json(); if (username === 'test' && password === '123') { return res( ctx.status(200), ctx.json({ token: 'fake-jwt-token', userId: 999 }) ); } return res(ctx.status(401), ctx.json({ error: 'Login failed' })); }) ];

然后在入口文件启用:

// src/index.js import { setupWorker } from 'msw'; import { handlers } from './mocks/handlers'; if (process.env.NODE_ENV === 'development') { const worker = setupWorker(...handlers); worker.start({ onUnhandledRequest: 'bypass' // 未匹配的请求放行给真实网络 }); }

实测下来,MSW 的稳定性极强。它最大的心智负担在于Service Worker 的缓存机制。首次访问页面时,SW 脚本会被安装并激活,之后即使你修改了handlers.js,浏览器仍可能运行旧版 SW。解决方案是:在 DevTools → Application → Service Workers 面板,勾选 “Update on reload”,并点击 “Skip waiting”。这个操作我每天至少做 3 次,已成为肌肉记忆。

2.3 方案三:Vite 插件级代理 + JSON Server(适合快速原型、设计评审)

当项目处于早期,接口文档只有 Swagger 页面,后端连第一个 commit 都没提交时,你需要的是“秒级生成可交互 API”。这时vite-plugin-mock+json-server组合拳就派上用场。它的核心逻辑是:用静态 JSON 文件定义数据结构,Vite 开发服务器在收到匹配请求时,直接读取 JSON 并返回,不启动任何额外服务。

首先,创建mock/user.json:

{ "users": [ { "id": 1, "name": "李四", "email": "lisi@example.com", "createdAt": "2023-01-01T00:00:00Z" } ] }

然后在vite.config.ts中配置:

import { defineConfig } from 'vite'; import { viteMockPlugin } from 'vite-plugin-mock'; export default defineConfig({ plugins: [ viteMockPlugin({ mockPath: 'mock', // 指向存放 .json/.ts 文件的目录 localEnabled: true, // 开发环境启用 prodEnabled: false, // 生产环境禁用 injectCode: ` import { createProdMockServer } from '../mock/index.ts'; if (import.meta.env.PROD) { createProdMockServer(); } `, logger: true }) ] });

此时访问http://localhost:3000/api/users,Vite 会自动将mock/user.json中的users数组作为响应体返回。更妙的是,它支持 RESTful 操作:GET /api/users/1返回单个用户,POST /api/users会向数组追加新对象(需配合json-server的内存模式)。我在某政务系统原型评审中,用这套方案 2 小时内搭出包含 12 个核心接口的 Mock 服务,UI 设计师当场用 Figma 链接真实数据流演示交互,客户直接拍板进入开发阶段。

注意:此方案的陷阱在于“过度拟真”。JSON Server 默认开启 CORS 和X-Total-Count响应头,但真实后端可能不返回这些。若前端组件依赖X-Total-Count做分页,而生产环境后端不提供,就会埋下线上 Bug。我的做法是:在mock/index.ts中显式覆盖响应头,确保 mock 与真实接口的 header 行为一致。

2.4 方案四:TypeScript Schema 驱动的自动化 Mock(适合大型项目、强类型约束)

当项目达到百人规模,接口数量破千,且前后端约定采用 OpenAPI 3.0 规范时,“手写 mock” 已成不可承受之重。这时必须升级到“代码即契约”阶段——用 TypeScript Interface 定义数据结构,用自动化工具生成符合该结构的随机数据。核心工具链是@sinclair/typebox+@sinclair/typegen。

第一步,定义类型(types/user.ts):

import { Type, Static } from '@sinclair/typebox'; export const UserSchema = Type.Object({ id: Type.Number({ minimum: 1 }), name: Type.String({ minLength: 2, maxLength: 20 }), email: Type.String({ format: 'email' }), roles: Type.Array(Type.String(), { minItems: 1, maxItems: 3 }), createdAt: Type.String({ format: 'date-time' }), avatarUrl: Type.Optional(Type.String({ format: 'uri' })) }); export type User = Static<typeof UserSchema>;

第二步,生成 Mock 数据(mocks/generate.ts):

import { Generate } from '@sinclair/typegen'; import { UserSchema } from '../types/user'; // 自动生成符合 UserSchema 约束的随机数据 export const mockUser = Generate.Value(UserSchema, { // 自定义字段生成规则 name: () => '测试用户' + Math.floor(Math.random() * 1000), email: () => `test${Math.floor(Math.random() * 100)}@example.com`, roles: () => ['user', 'editor', 'admin'].sort(() => 0.5 - Math.random()).slice(0, 2) }); console.log(mockUser); // 输出示例: // { id: 42, name: "测试用户789", email: "test42@example.com", roles: ["user", "editor"], createdAt: "2023-06-15T10:30:45.123Z" }

第三步,在 MSW handler 中复用:

import { rest } from 'msw'; import { mockUser } from './generate'; export const handlers = [ rest.get('/api/user/:id', (req, res, ctx) => { return res(ctx.status(200), ctx.json(mockUser)); }) ];

这套方案的价值在于:类型变更即 Mock 变更。当后端在 OpenAPI 文档中新增status: enum['active','inactive']字段时,你只需更新UserSchema,Generate.Value()会自动产出带status的 mock 数据,且保证值必为'active'或'inactive'。我在某金融风控系统中推行此方案后,前端 mock 数据维护成本下降 70%,接口字段遗漏率归零。代价是学习曲线陡峭——你需要理解 TypeBox 的 DSL 语法,但一旦掌握,它带来的确定性远超初期投入。

3. Mock 数据的“灵魂”:如何让假数据不露馅?

写过 mock 的人都知道,最难的不是“怎么返回数据”,而是“怎么让数据看起来像真的”。一个id: 1的用户,和一个id: 1234567890123456789的用户,在前端组件里可能引发完全不同的渲染逻辑(比如 ID 超过 16 位触发科学计数法显示)。Mock 数据的“可信度”,直接决定测试的有效性。以下是我在多个项目中沉淀出的 5 条黄金法则,每一条都来自血泪教训。

3.1 法则一:ID 必须是“有含义的假”——拒绝自增数字

新手常犯的错误是:id: 1,id: 2,id: 3。这在列表渲染时看似无害,但一旦涉及关联查询(如GET /api/orders?userId=1),问题就来了。真实数据库中,用户 ID 是 UUID 或雪花算法生成的长整型,其长度、格式、分布规律都影响着前端逻辑。例如,某订单组件会检查userId.toString().length > 10来判断是否为“新用户”,用id: 1测试永远走不到“新用户”分支。

正确做法是:用真实 ID 生成策略的轻量版。对于 UUID,用crypto.randomUUID()(现代浏览器支持)或uuid/v4库;对于雪花 ID,用nanoid生成 12-16 位随机字符串。更进一步,可以按业务含义编码:用户 ID 以U_开头,订单 ID 以O_开头,便于在日志和调试中一眼识别来源。

// mock/utils/id.ts export const generateUserId = () => `U_${nanoid(12)}`; export const generateOrderId = () => `O_${nanoid(14)}`; // 使用 const user = { id: generateUserId(), // "U_xk8fLm2pQr9t" name: "王五" };

3.2 法则二:时间字段必须“活”起来——拒绝固定时间戳

createdAt: "2023-01-01T00:00:00Z"这样的时间戳,会让所有“最近 7 天订单”、“今日活跃用户”类功能彻底失效。Mock 时间必须具备相对性、随机性、合理性。我的标准模板是:

  • createdAt: 随机生成过去 30 天内的任意时间(new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000))
  • updatedAt: 在createdAt基础上随机增加 0-10 分钟(模拟编辑行为)
  • expiresAt: 在createdAt基础上增加 1-7 天(模拟 Token 过期)
export const randomDateInPastDays = (days: number) => { const now = Date.now(); const pastMs = Math.random() * days * 24 * 60 * 60 * 1000; return new Date(now - pastMs).toISOString(); }; export const mockUser = { createdAt: randomDateInPastDays(30), updatedAt: new Date( new Date(randomDateInPastDays(30)).getTime() + Math.random() * 10 * 60 * 1000 // 0-10 分钟 ).toISOString(), expiresAt: new Date( Date.now() + (1 + Math.random() * 6) * 24 * 60 * 60 * 1000 ).toISOString() };

3.3 法则三:关联数据必须“成对出现”——拒绝孤儿记录

GET /api/users/123返回用户 A,但GET /api/orders?userId=123却返回空数组——这种“数据孤岛”会让前端陷入无限 loading。Mock 数据必须体现业务关系的完整性。例如,一个用户至少有 1-3 个订单,每个订单至少有 1-2 个商品项。我习惯用嵌套生成函数:

export const mockOrder = (userId: string) => ({ id: generateOrderId(), userId, items: Array.from({ length: Math.floor(Math.random() * 2) + 1 }, () => ({ productId: generateProductId(), quantity: Math.floor(Math.random() * 5) + 1, price: parseFloat((Math.random() * 1000).toFixed(2)) })), totalAmount: 0, // 后续计算填充 status: ['pending', 'shipped', 'delivered'][Math.floor(Math.random() * 3)] as const }); export const mockUserWithOrders = (userId: string) => { const orders = Array.from({ length: Math.floor(Math.random() * 3) + 1 }, () => mockOrder(userId) ); // 计算总金额 const totalAmount = orders.reduce((sum, order) => sum + order.items.reduce((itemSum, item) => itemSum + item.price * item.quantity, 0), 0 ); return { ...mockUserBase(userId), orders, totalOrderAmount: parseFloat(totalAmount.toFixed(2)) }; };

3.4 法则四:边界值必须“刻意制造”——拒绝皆大欢喜

真实世界充满异常:空数组、null 字段、超长字符串、特殊字符(emoji、中文标点)、布尔值反转。如果 mock 数据全是“理想状态”,前端永远不会发现user.name?.split(' ')[0]在name: null时崩溃。我的做法是:为每个字段定义 3 种状态:正常值、边界值、异常值,并在不同 handler 中轮换使用。

// mock/data/edgeCases.ts export const edgeCaseUser = { // 正常值 name: "赵六", // 边界值:超长字符串(模拟数据库 varchar(255) 截断) email: "a".repeat(254) + "@example.com", // 异常值:null,触发空值处理逻辑 avatarUrl: null, // 边界值:空数组,测试列表为空的 UI roles: [] as string[], // 异常值:包含 emoji,测试国际化和渲染兼容性 bio: "热爱编程 💻 | 喜欢咖啡 ☕ | 🌍 旅行者" }; // 在特定 handler 中使用 rest.get('/api/user/edge', (req, res, ctx) => { return res(ctx.status(200), ctx.json(edgeCaseUser)); });

3.5 法则五:响应体结构必须“零差异”——拒绝“多一个字段少一个字段”

这是最隐蔽也最致命的坑。后端返回{ data: { user } },你 mock 成{ user },前端解构res.data.user就会报错。Mock 的第一铁律是:响应体结构必须与真实接口文档 100% 一致,包括外层包装、字段命名、大小写、空格。我强制要求团队:所有 mock 数据必须基于 Swagger/OpenAPI JSON Schema 生成,或由后端提供一份sample-response.json作为基准。在代码审查中,我会逐字段比对 mock 响应与 sample-response 的 diff,哪怕只是一个created_at和createdAt的命名差异,也必须修正。

4. 从开发到上线:Mock 的全生命周期管理实践

Mock 不是写完就扔的临时脚手架,而是一个需要贯穿项目全生命周期的“活”系统。我见过太多项目,初期用 MSW 写得风生水起,到了提测阶段,测试同学抱怨“mock 数据和后端不一致”,上线前夜发现mock/handlers.ts里还留着ctx.delay(5000)导致首屏加载 5 秒。以下是我总结的 Mock 全流程管理清单,已在 3 个百万级用户项目中验证有效。

4.1 阶段一:设计与约定(项目启动期)

这是最关键的预防性工作。很多 Mock 问题,根源在于前期缺乏共识。我的标准动作是:

  • 召开 Mock 约定会议:前端、后端、测试三方参加,明确:
    • 接口文档格式(Swagger/YAML/TS Interface)
    • Mock 数据来源(后端提供 sample-response,还是前端基于 schema 生成)
    • 字段含义与业务规则(如status: 'active' | 'inactive' | 'pending',pending是否允许登录)
    • 响应头要求(X-Total-Count,Link分页头,CORS 策略)
  • 建立 Mock 数据字典:用 Markdown 表格维护,包含字段名、类型、示例值、是否必填、业务规则、mock 策略(随机/固定/关联)。例如:
字段类型示例值必填业务规则Mock 策略
idstring"U_xk8fLm2pQr9t"是长度 12-16,前缀U_nanoid(12)
emailstring"test123@example.com"是符合 email 格式随机生成
lastLoginAtstring"2023-06-15T10:30:45Z"否为空表示从未登录null或随机过去 30 天
  • 制定 Mock 代码规范:明确文件位置(src/mock/)、命名规则(user.handlers.ts)、导出方式(统一handlers数组)、禁用项(禁止硬编码 URL,必须用常量)。

4.2 阶段二:开发与迭代(编码期)

此阶段的核心是“让 Mock 成为开发的一部分,而非额外负担”。我的实践是:

  • 与组件开发同步进行:在写UserProfileCard.tsx组件前,先写好GET /api/user/:id的 handler。组件内直接useQuery(['user', id], fetchUser),数据流天然对齐。
  • 利用 IDE 智能提示:在 handler 中,return res(ctx.json(user))的user对象必须是User类型。VS Code 会实时校验字段缺失,强迫你补全avatarUrl、bio等字段。
  • 版本化 Mock 数据:mocks/v1/存放稳定版,mocks/v2/存放新接口。通过 URL 路径区分,避免破坏现有功能。例如GET /api/v1/users和GET /api/v2/users可指向不同 handler。

4.3 阶段三:测试与联调(提测期)

这是 Mock 价值最大化的阶段。我的关键动作:

  • 为测试同学提供“一键切换”开关:在 Vite/HMR 环境中,添加一个全局变量window.__MOCK_MODE__,值为'all'(全部 mock)、'none'(直连后端)、'partial'(仅部分接口 mock)。测试同学按Ctrl+Shift+M即可切换,无需重启服务。
  • 生成 Mock 数据覆盖率报告:用脚本扫描所有rest.*handler,统计覆盖的 URL 模式、HTTP 方法、状态码。输出 HTML 报告,标注“未覆盖接口”,驱动后端补齐 sample-response。
  • 联调前“Mock-Real Diff”检查:用curl获取真实后端响应,与 Mock 响应做 JSON Diff(推荐json-diffCLI 工具)。重点检查:字段名大小写、空值处理(nullvsundefined)、数组长度(空数组 vs 无字段)、时间格式(ISO 8601 vs Unix timestamp)。

4.4 阶段四:上线与归档(发布期)

Mock 的终点不是删除,而是“退役”。我的上线 checklist:

  • 生产环境绝对禁用:在vite.config.ts和main.ts中,用process.env.NODE_ENV === 'production'严格包裹所有 Mock 初始化代码。CI 构建时,NODE_ENV=production确保零风险。
  • 保留历史 Mock 版本:mocks/archive/v1.2.0/目录存档上线时的 Mock 代码。当线上出现数据相关 Bug,可快速回滚对比,确认是后端变更还是前端逻辑问题。
  • 撰写 Mock 迁移文档:记录本次上线中,哪些 Mock 接口已对接真实后端,哪些仍需保留(如第三方服务降级方案),哪些已废弃。这份文档是下个迭代的起点。

注意:曾有个项目因疏忽,在生产环境index.html中意外注入了 MSW 的worker.js脚本。虽然 SW 未激活(因worker.start()被条件编译掉),但 Chrome 仍会尝试下载该脚本,导致 404 请求污染监控日志。我的补救措施是:在vite.config.ts的build.rollupOptions.output.manualChunks中,将worker.js显式排除,确保它不会被打包进生产产物。

5. 那些年,我们共同踩过的 Mock 坑与避坑指南

再完美的方案,也挡不住人在疲惫时的手滑。以下是我和团队在过去三年里,用真金白银(加班费、上线事故、客户投诉)换来的 7 个高频、高危、高隐蔽性坑位。每一个都附带“症状-根因-解法-验证方式”,可直接抄作业。

5.1 坑位一:MSW 的 “Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window'”

症状:页面白屏,控制台报错,指向fetch调用处,但代码明明没改。

根因:MSW 的 Service Worker 在页面加载时未完成注册/激活,而组件已发起fetch请求。常见于:Vite HMR 热更新后,SW 脚本被重新加载,但旧 SW 仍在控制网络,新 SW 未接管,导致请求被“丢弃”。

解法:在setupWorker后,添加等待逻辑,并在组件中封装容错 fetch:

// utils/fetchWithMock.ts export const safeFetch = async (input: RequestInfo, init?: RequestInit) => { try { return await fetch(input, init); } catch (error) { // 捕获 MSW 未就绪时的 fetch 失败 if (error instanceof TypeError && error.message.includes('fetch')) { console.warn('MSW not ready, retrying in 100ms...'); await new Promise(resolve => setTimeout(resolve, 100)); return await fetch(input, init); } throw error; } }; // 组件中使用 const { data } = useQuery(['user'], () => safeFetch('/api/user/1').then(r => r.json()));

验证:手动禁用浏览器 SW,刷新页面,观察是否平滑降级为真实请求。

5.2 坑位二:Mock 数据中的undefined字段,导致 React 渲染崩溃

症状:Cannot read property 'name' of undefined,但console.log(data)显示数据存在。

根因:Mock 数据中,user.profile字段为undefined,但组件代码user.profile.name未做可选链(?.)或空值合并(??)处理。

解法:强制所有 Mock 数据字段初始化。绝不允许undefined,用null或默认值替代:

// ❌ 错误:profile 字段可能为 undefined const user = { id: 1, name: "钱七" // profile 缺失 }; // ✅ 正确:profile 字段始终存在,值为 null 或默认对象 const user = { id: 1, name: "钱七", profile: null // 或 { bio: "", avatarUrl: "" } };

并在 ESLint 中启用@typescript-eslint/no-unnecessary-condition规则,自动检测未处理的undefined访问。

5.3 坑位三:Vite Mock 插件在生产环境意外生效

症状:上线后,部分用户反馈数据异常,日志显示请求被重定向到mock/xxx.json。

根因:vite-plugin-mock的prodEnabled配置被误设为true,或 CI 构建时NODE_ENV环境变量未正确设置为production。

解法:双重保险。第一重,在插件配置中显式关闭:

viteMockPlugin({ prodEnabled: false, // 强制为 false localEnabled: true, })

第二重,在vite.config.ts顶部添加环境校验:

if (process.env.NODE_ENV === 'production' && process.env.VITE_MOCK_ENABLED === 'true') { throw new Error('VITE_MOCK_ENABLED must be false in production!'); }

CI 脚本中,确保VITE_MOCK_ENABLED=false。

5.4 坑位四:时间字段时区混乱,导致“今天”变“昨天”

症状:createdAt: "2023-06-15T00:00:00.000Z"在北京时间显示为“6月14日”。

根因:Mock 数据生成时用了new Date().toISOString(),返回 UTC 时间,但前端组件用toLocaleDateString()渲染时,按本地时区转换,造成日期偏移。

解法:Mock 数据中的时间字段,统一存储为本地时区字符串,或明确标注时区信息。推荐方案是:存储为 ISO 8601 格式,但包含时区偏移(+08:00):

// 生成北京时间(UTC+8)的时间字符串 export const nowInBeijing = () => { const now = new Date(); const offset = 8; // 北京时区 const utc = now.getTime() + (now.getTimezoneOffset() * 60000); const targetTime = new Date(utc + (3600000 * offset)); return targetTime.toISOString().slice(0, -1) + '+08:00'; }; // 输出:"2023-06-15T15:30:45.123+08:00"

5.5 坑位五:Mock 接口返回 200,但业务逻辑期望 401

症状:登录态失效时,前端未跳转登录页,用户可继续操作。

根因:Mock handler 只实现了200成功路径,未模拟401 Unauthorized、403 Forbidden、429 Too Many Requests等业务错误状态。

解法:为每个接口编写完整的状态矩阵。例如登录接口:

rest.post('/api/login', async (req, res, ctx) => { const { username, password } = await req.json(); if (username === 'invalid' || password === 'wrong') { return res(ctx.status(401), ctx.json({ error: 'Invalid credentials' })); } if (username === 'rate-limited') { return res(ctx.status(429), ctx.json({ error: 'Too many requests' })); } return res(ctx.status(200), ctx.json({ token: 'xxx', userId: 1 })); });

并在组件中,用queryClient.setQueryData主动注入错误状态,测试错误边界。

5.6 坑位六:JSON Server 的内存模式导致数据“越界”

症状:在/api/users列表页添加新用户后,/api/users/123详情页却查不到该用户。

根因:json-server的内存模式(--watch)在开发时会监听 JSON 文件变化,但POST请求是写入内存,不写入磁盘文件。当 Vite 服务重启,内存数据丢失,文件未更新,导致数据不一致。

解法:禁用内存模式,强制写入文件。使用 `json-server --watch db

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 12:42:13

内核可编程与AI资源控制:混部场景的调度优化实践

1. 从调度困境说起&#xff1a;为什么内核"可编程"成了必选项前阵子帮团队排查一个线上问题&#xff0c;印象很深。某个核心链路服务在晚高峰会规律性地出现P99延迟抖动&#xff0c;从不到20毫秒直接飙到200毫秒开外。第一反应是看CPU利用率——好&#xff0c;整体才…

作者头像 李华
网站建设 2026/10/21 12:42:12

Java秒杀系统从0到1开发实战:高并发库存防超卖方案

秒杀系统这个选题&#xff0c;在Java后端相关项目里算是常青树了。很多同学毕业设计选它&#xff0c;想着无非就是商品秒杀、生成订单、定时开抢&#xff0c;结果一做才发现远没那么简单。不带夸张地说&#xff0c;凡是做秒杀系统翻车的&#xff0c;八成都是栽在库存超卖和高并…

作者头像 李华
网站建设 2026/10/21 12:41:58

基于STM32的自动喂食器设计:从硬件选型到代码实现

简介&#xff1a;这是一份基于STM32F401的自动喂食器嵌入式课程设计完整项目包&#xff0c;面向嵌入式方向的学生和开发者&#xff0c;可用于宠物喂养、定时控制等场景的参考设计。资源共201个文件&#xff0c;以C/H源码和Proteus仿真文件为核心&#xff0c;另有Keil工程配置、…

作者头像 李华
网站建设 2026/10/21 12:40:22

鲲鹏ARM架构部署GitLab全攻略:Docker安装、性能调优与踩坑实录

1. 为什么要在鲲鹏架构上折腾GitLab 第一次在鲲鹏服务器上装GitLab的人&#xff0c;大概率会经历一个"以为十分钟搞定&#xff0c;结果折腾一整天"的过程。原因很简单&#xff1a;GitLab官方提供的安装包和镜像&#xff0c;绝大多数是面向x86_64架构的&#xff0c;而…

作者头像 李华
网站建设 2026/10/21 12:38:12

C#仓库管理系统实战:WinForm+ADO.NET三层架构与库存事务处理

简介&#xff1a;这是一套面向高校计算机相关专业学生与.NET初学者的C#仓库管理系统完整项目源码&#xff0c;可作为课程设计、毕业设计或C#桌面开发练手参考。系统功能覆盖换班管理、单据查询、销售分析、库存管理、统计报表、日常管理与系统设置等模块&#xff0c;并借助Iris…

作者头像 李华
网站建设 2026/10/21 12:38:08

Sicar-Training-show-V16 程序包上机调试与参数配置实战指南

简介&#xff1a;Sicar-Training-show-V16程序是一套面向工业自动化领域学习者的Sicar标准程序训练示例&#xff0c;适合正在接触西门子PLC、PROFINET通信及分布式IO组态的工程师与学员&#xff0c;用于理解标准程序框架、设备描述文件与项目工程结构。压缩包共106个文件&#…

作者头像 李华