大家好,我是 一步梅。
做前端开发这些年,我见过太多因为接口问题导致的扯皮:后端字段改了个名字,前端页面直接白屏;联调前手写了半天 TypeScript 类型,结果联调时发现类型全是错的;后端接口没写完,前端只能自己手写硬编码的假数据。
今天这篇文章,我不聊虚的概念,直接上工程实操。带大家把 OpenAPI 规范(以前叫 Swagger) 作为团队唯一的“契约源头”,通过一套工程化脚本,自动生成前端 TypeScript 类型、强类型的请求 SDK 以及开箱即用的本地 Mock 服务。
一、 核心思路:把 OpenAPI 当作“单一真实源”
所谓的“契约驱动开发”(Contract-First),核心逻辑非常简单:代码不应该领先于文档,文档也不应该落后于代码,文档(契约)本身就是生成代码的源头。
整个流水线的设计如下:
[后端导出 / 设计输出]
↓
OpenAPI 规范文件 (schema.json / schema.yaml)
↓
┌─────────────────┬─────────────────┐
↓ ↓ ↓
TS 类型定义 请求 SDK 本地 Mock 服务
(types.ts) (api.ts) (Prism / MSW)
前端不再手写任何 interface UserResponse { ... },只要规范文件一更新,一行命令全量同步。
二、 准备契约文件:一个标准的 OpenAPI 示例
我们以一个简单的用户查询接口为例。假设后端的 OpenAPI 3.0 规范文件 openapi.yaml 如下:
openapi: 3.0.0
info:
title: 用户服务 API
version: 1.0.0
paths:
/api/v1/users/{id}:
get:
summary: 获取指定用户信息
operationId: getUserById
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: 成功返回
content:
application/json:
schema:
$ref: '#/components/schemas/User'
components:
schemas:
User:
type: object
required:
- id
- name
- email
properties:
id:
type: integer
example: 1001
name:
type: string
example: "一步梅"
email:
type: string
format: email
example: "yibumei@example.com"
role:
type: string
enum: [admin, editor, viewer]
default: viewer
三、 第一步:自动生成 TypeScript 类型
手写类型不仅累,而且极其容易漏掉可选属性(optional)或枚举值。
社区里生成 TS 类型最纯粹、零运行时代价的工具是 openapi-typescript。
1. 安装依赖
npm install -D openapi-typescript
2. 配置 NPM 脚本
在 package.json 中配置生成脚本:
{
"scripts": {
"gen:types": "openapi-typescript ./openapi.yaml -o ./src/api/schema.d.ts"
}
}
3. 执行生成
运行 npm run gen:types 后,你会得到一个完全匹配后端的类型文件 src/api/schema.d.ts:
export interface paths {
"/api/v1/users/{id}": {
get: operations["getUserById"];
};
}
export interface components {
schemas: {
User: {
id: number;
name: string;
email: string;
role?: "admin" | "editor" | "viewer";
};
};
}
export interface operations {
getUserById: {
parameters: {
path: {
id: number;
};
};
responses: {
200: {
content: {
"application/json": components["schemas"]["User"];
};
};
};
};
}
所有的枚举、必填项、路径参数全部被精准映射,不再需要人工核对。
四、 第二步:生成带完整类型推导的请求 SDK
有了类型还不够,如果每次发请求还要手动指定泛型,依然可能出错。我们可以借助 openapi-fetch(轻量、高性能、与 openapi-typescript 原生适配)。
1. 安装依赖
npm install openapi-fetch
2. 初始化客户端
新建 src/api/client.ts:
import createClient from "openapi-fetch";
import type { paths } from "./schema";
export const apiClient = createClient<paths>({
baseUrl: "https://api.example.com"
});
3. 在业务代码中调用
得益于类型系统的深度结合,路径、入参、出参都会自动提示:
import { apiClient } from "./api/client";
async function fetchUserInfo(userId: number) {
// 1. 输入 "/api/v1/users/{id}" 时会有 IDE 自动补全
// 2. 如果缺少 params.path.id,TypeScript 会直接报错
const { data, error } = await apiClient.GET("/api/v1/users/{id}", {
params: {
path: { id: userId },
},
});
if (error) {
console.error("请求失败", error);
return;
}
// 3. 此时 data 会被自动推导为 components['schemas']['User']
console.log(`用户姓名: ${data.name}, 角色: ${data.role}`);
}
好处在哪?
如果后端把 id 从必填改成不要了,或者删除了某个字段,前端只要拉取最新规范重新生成,TypeScript 编译器就会在所有调用出问题的地方直接标红,根本不用等到联调期去盲测。
五、 第三步:零代码生成本地 Mock 服务
后端开发往往滞后于前端。过去我们习惯在前端代码里写假数据,联调前还得再把假数据删掉。
既然已经有了 OpenAPI 规范,我们可以使用 Stoplight 出品的 Prism,基于规范定义直接拉起一个 Mock 服务。
1. 安装依赖
npm install -D @stoplight/prism-cli
2. 配置启动脚本
在 package.json 添加命令:
{
"scripts": {
"mock": "prism mock ./openapi.yaml -p 4010"
}
}
3. 启动并测试
运行 npm run mock,Prism 会读取 YAML 文件中的 example 和类型定义,在本地 http://127.0.0.1:4010 启动服务:
[CLI] … INFO Using OpenAPI 3.0.0 specification
[CLI] ✔ info Serving 1 path on http://127.0.0.1:4010
[CLI] ℹ info GET http://127.0.0.1:4010/api/v1/users/1001
用 curl 发送请求:
curl http://127.0.0.1:4010/api/v1/users/1001
返回内容直接符合我们在 YAML 中定义的格式:
{
"id": 1001,
"name": "一步梅",
"email": "yibumei@example.com",
"role": "viewer"
}
更棒的是: 如果你传错参数(比如传了一个字符串作为 ID),Prism 还会严格按照 OpenAPI 规则报错,提前帮你校验请求参数。
六、 落地建议与常见“坑点”
把这套体系在团队内铺开时,通常会遇到以下三个现实问题:
- 后端文档更新了,前端不知道:
- 解法: 在 CI/CD 流程中增加触发器。后端发布测试环境后,自动通过 Webhook 通知前端工程拉取最新的
openapi.json,在前端仓库自动提 PR 甚至跑集成检查。
- 解法: 在 CI/CD 流程中增加触发器。后端发布测试环境后,自动通过 Webhook 通知前端工程拉取最新的
- 规范文件中的
operationId重复或缺失:- 解法: 规范必须写进团队开发手册。如果后端使用 Spring Boot(Springdoc)或 NestJS(Swagger 模块),一定要配置好装饰器,确保生成唯一的标识符。
- 命名混乱与类型膨胀:
- 解法: 契约规范也是代码,需要做 Code Review。严格避免出现
field1,field2这类无意义命名,保持description和example完整,这直接决定了前端生成的代码可读性。
- 解法: 契约规范也是代码,需要做 Code Review。严格避免出现
总结
文档工程化并不是“写完代码再补文档”,而是用工程手段让文档成为生产力工具。
通过「OpenAPI 契约 -> 自动化类型 -> 强类型客户端 -> 真实 Mock 服务」这条链路:
- 前端: 免去了手动维护 TS 类型和假数据的成本,开发体验拉满。
- 后端: 接口改动带来的破坏性更新,可以通过编译期报错立刻被前端发现。
- 团队: 沟通成本大幅压缩,把时间还给真正的业务逻辑。
这套方案依赖的所有工具都是轻量、开源且解耦的,花半天时间接入,收益会贯穿整个项目的生命周期。建议大家在下一个新项目中直接试一试。
License: CC BY-NC 4.0
Updated 13 hours ago
Was this article helpful? Give it a like.
0 comments


