本文介绍如何通过泛型和条件类型,为同一接口中相互关联的属性(如 geometryType 与 shape)建立类型约束,使 shape 的具体类型能根据 geometryType 的字面量值自动推导,从而提升类型安全性和开发体验。
本文介绍如何通过泛型和条件类型,为同一接口中相互关联的属性(如 `geometrytype` 与 `shape`)建立类型约束,使 `shape` 的具体类型能根据 `geometrytype` 的字面量值自动推导,从而提升类型安全性和开发体验。
在地理信息系统(GIS)或图形建模等场景中,常需用统一接口描述多种几何对象(如圆形、多边形、点、椭圆),但每种类型对应不同的字段结构。若将所有子类型简单联合(如 shape: ICircle | IPolygon | ...),会丧失类型精度——编译器无法识别 geometryType === GeometryType.CIRCLE 时 shape 必然具备 center 和 radius 字段。
解决方案是采用泛型条件接口:将 IGeometry 定义为接受一个受约束的泛型参数 T extends GeometryType,再利用 TypeScript 的分布式条件类型(T extends X ? A : T extends Y ? B : ...)精确映射每个枚举值到其对应的形状类型。
以下是完整实现:
export enum GeometryType { CIRCLE = 4, POLYGON = 5, POINT = 6, ELLIPSE = 7}export interface ICircle { center: number; radius: number; }export interface IPolygon { lat: number; lon: number; }export interface IPoint { lat: number; lon: number; }export interface IEllipse { yAxis: number; xAxis: number; angle: number; }export interface IGeometry<T extends GeometryType> { geometryType: T; shape: T extends GeometryType.CIRCLE ? ICircle : T extends GeometryType.POLYGON ? IPolygon : T extends GeometryType.POINT ? IPoint : T extends GeometryType.ELLIPSE ? IEllipse : never; // 确保枚举全覆盖,防止新增值未处理}
该设计的关键优势在于类型即文档 + 编译期校验。例如:
// ✅ 正确:geometryType 与 shape 类型严格匹配const circle: IGeometry<GeometryType.CIRCLE> = { geometryType: GeometryType.CIRCLE, shape: { center: 0, radius: 5 }};// ❌ 编译报错:geometryType 是 CIRCLE,但 shape 缺少 radiusconst invalidCircle: IGeometry<GeometryType.CIRCLE> = { geometryType: GeometryType.CIRCLE, shape: { center: 0 } // Error: Property 'radius' is missing};// ✅ 支持类型推导(无需显式标注)const point = { geometryType: GeometryType.POINT as const, shape: { lat: 31.23, lon: 121.47 }} satisfies IGeometry<GeometryType.POINT>; // TypeScript 4.9+ 推荐写法
⚠️ 注意事项:
这种模式不仅适用于几何建模,还可推广至表单配置、协议消息、状态机等任何“类型标识符 + 对应数据结构”的场景,是 TypeScript 高级类型编程的核心实践之一。