如何使用TypeScript定义Web API类型并供跨仓库客户端复用
TypeScript跨仓库共享接口类型实现方案
没有绝对的“最优解”,根据你项目的代码管理模式选对应方案即可,按推荐优先级从高到低排列:
- 如果你服务端和客户端同属一个Monorepo仓库,完全不需要额外发布npm包:把公共类型定义放到Monorepo的公共package目录下,通过内部工作区依赖直接引用即可,版本同步零成本。
- 如果服务端、客户端是完全独立的两个代码仓库,优先选带运行时校验的SDK包方案,比纯类型声明包靠谱得多:
单独发布.d.ts类型包的最大问题是“类型和实际返回数据靠人工维护一致性”,只要后端改接口字段忘了同步更新类型包,客户端拿到的TS类型就是错的,编译阶段完全不报错,上线直接出问题。
更稳妥的做法是把类型定义和接口运行时校验逻辑绑定,发布成轻量的内部SDK包:- 用zod/valibot这类库先定义接口结构的运行时校验Schema
- 直接从Schema推导TS类型,不需要重复手写interface,从根源避免定义不一致
- 包内可以选择性导出校验函数、甚至封装好的接口请求方法,客户端引入后既能拿到类型提示,也能在接口返回不符合结构时提前捕获异常
示例代码如下:
这类包直接发布到公司内部私有npm源即可,客户端安装引入后TS会自动识别类型,不需要额外配置。import { z } from 'zod'; // 运行时校验规则 export const DeviceSchema = z.object({ name: z.string(), address: z.number() }); // 自动推导TS类型,和你手写的Device接口完全一致 export type Device = z.infer<typeof DeviceSchema>; // 可选:封装好的请求方法,客户端直接调用即可 export async function fetchDeviceList(): Promise<Device[]> { const rawData = await fetch('/api/devices').then(res => res.json()); // 自动校验返回数据结构,不符合预期直接抛错 return z.array(DeviceSchema).parse(rawData); } - 如果你确定不需要任何运行时代码、只想共享纯类型定义,再考虑发布独立的类型包:
不需要手写.d.ts文件,直接维护写了interface/type的普通.ts文件即可,配置TS编译时开启declaration: true,TS会自动生成对应的类型声明文件,发布npm包时在package.json里配置types字段指向生成的入口声明文件,客户端安装后就能正常导入使用。
注意一定要做好版本号管理,接口变更时同步升级包版本,避免客户端拿到过时的类型定义。
不推荐使用git subtree、手动复制类型文件、CDN托管声明文件这类方案,这类方案版本同步成本极高,非常容易出现类型和实际接口不一致的问题,排查成本很高。
内容的提问来源于stack exchange,提问作者Ira Klein
相关产品推荐
相关产品推荐

