零源码修改切换 mock 的关键是依赖可替换而非简单封装,需将 requestfn 作为参数传入 hook/service,统一用工厂函数创建服务实例并注入,mock 必须与真实接口保持相同返回结构、类型定义和错误契约。

只要所有请求调用不硬编码在业务逻辑里,就能做到零源码修改切换 Mock —— 关键不是“加一层封装”,而是让 fetch、axios.get、new ApiClient() 这些依赖彻底可替换。
把 requestFn 当作参数传给 Hook 或 Service
硬编码 fetch('/api/user') 是最常见也最致命的写法。它锁死了执行环境,无法被 Jest 拦截,也无法在测试中注入错误响应。
- 正确做法是让 Hook 接收一个函数参数:
useUser(requestFn = fetch),内部调用requestFn('/api/user') - 生产环境直接调用
useUser()(默认用原生fetch) - 测试时传入
useUser(mockFetch),其中mockFetch可返回任意结构、状态码、延迟或抛错 - 若项目用
axios,同样适用:useUser(axios.get)或useUser(apiClient.getUser),无需改 Hook 内部逻辑
用工厂函数统一创建服务实例,别满世界 new
看到 new ApiClient() 出现在多个文件里,说明解耦还没开始——这不是 DI,是批量字符串替换预备态。
PigX UI Pro 前端开发指南 - Vue 3 + TypeScript + Element Plus。当用户提到 PigX UI、PigX 前端、lgb-mgui 项目、Vue 3 企业级后台开发、Element Plus 后台开发时使用此技能。
- 新建
services.ts,只导出工厂函数:export const createApiClient = (options: { baseUrl: string }) => new ApiClient(options) - 在入口或顶层组件中一次性构造并注入:
const services = { api: createApiClient({ baseUrl: import.meta.env.VITE_API_URL }) } - 所有业务代码都从
services.api取实例,而不是自己new - Mock 场景下,只需让
createApiClient返回mockApiClient,其余代码完全不动
别把 Vue provide/inject 当 DI 容器用
provide('api', new ApiClient()) 看似解耦,实则埋雷:实例在 provide 时就已创建,无法延迟初始化,也无法复用同一实例,类型校验也难做。
- ✅ 正确用法:
provide('services', services)—— 提供的是已构造好的稳定对象 - ❌ 错误用法:
provide('api', new ApiClient())—— 每次provide都新建,且生命周期失控 - 如果必须按需初始化(比如带 token 的 client),工厂函数应返回函数:
createApiClient = () => () => new ApiClient(...),调用方自行控制时机
适配层比 Mock 本身更重要
很多人以为 Mock 就是“把 mockData 替换成 await axios.get()”,但同步数据和异步响应在 UI 层表现完全不同:加载态、错误态、缓存策略、重试逻辑都会崩。
- 真实接口返回
Promise,Mock 数据必须也返回同结构 Promise,不能直接return mockData - 推荐统一包装成函数:
export const getUser = () => (useMock ? Promise.resolve(mockUser) : apiClient.getUser()) - 确保返回值类型一致、字段层级一致、错误格式一致(比如都走
.catch()或都用try/catch) - 边界情况要提前覆盖:空数组、null 字段、401 登录过期、503 服务不可用 —— 这些不能等上线后才发现
最容易被忽略的一点:Mock 不是临时补丁,它必须和真实接口共享同一套类型定义与错误处理契约。否则所谓“一键切换”,切过去的第一分钟就会爆红。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










