如何定义带层级泛型及其他字段的TypeScript接口?
TypeScript 层级位置类型定义实现问题
需求说明
我希望通过泛型类型 LocationBase 定义一组层级嵌套的位置类型:
type Region = LocationBase<1, "region", Subregion>; type Subregion = LocationBase<2, "subregion", Area>; type Area = LocationBase<3, "area">;
使其等价于手动编写的以下接口结构:
interface Region { level: 1; levelName: "region"; children?: Subregion[]; } interface Subregion { level: 2; levelName: "subregion"; children?: Area[]; } interface Area { level: 3; levelName: "area"; children?: Location<4>; } interface Location<T extends number>{ level: T; levelName: "location"; children?: Location<PlusOne<T>>; }
请问这个需求能否实现?
尝试的代码及报错
我编写了以下代码,但触发了两个TypeScript错误:
TS2716: Type parameter 'Child' has a circular default.
TS2344: Type 'PlusOne' does not satisfy the constraint 'number'
代码如下:
type ArrayWithLength<Len extends number, Arr extends never[] = []> = Arr["length"] extends Len ? Arr : ArrayWithLength<Len, [never, ...Arr]>; type PlusOne<T extends number> = [unknown, ...ArrayWithLength<T>]["length"]; interface LocationBase< Level extends number = number, LevelName extends string = "location", Child = LocationBase, // Error > { level: Level; levelName: LevelName; children?: Child[]; }; type Region = LocationBase<1, "region", Subregion>; type Subregion = LocationBase<2, "subregion", Area>; type Area = LocationBase<3, "area", LocationBase<4>>; // type Level5ArrayExpected = LocationBase<4>["children"]; // unknown[]
解决方案
这个需求是可以实现的,核心是修复泛型默认值的循环引用问题,同时确保PlusOne工具类型的返回值满足number约束。以下是正确的实现代码:
方案1:简化的PlusOne实现
// 实现数字自增的类型工具,覆盖常用层级范围,超出范围返回number type PlusOne<T extends number> = T extends 0 ? 1 : T extends 1 ? 2 : T extends 2 ? 3 : T extends 3 ? 4 : T extends 4 ? 5 : number; // 重新定义LocationBase,Child默认值为下一级的LocationBase interface LocationBase< Level extends number = number, LevelName extends string = "location", Child = LocationBase<PlusOne<Level>> > { level: Level; levelName: LevelName; children?: Child[]; } // 定义目标层级类型 type Region = LocationBase<1, "region", Subregion>; type Subregion = LocationBase<2, "subregion", Area>; type Area = LocationBase<3, "area">; // 不传入Child时,自动关联LocationBase<4> // 验证递归层级 type Level4Location = LocationBase<4>; type Level5Children = Level4Location["children"]; // 类型为 LocationBase<5>[] | undefined
方案2:通用数组式PlusOne实现
如果需要支持任意数字的自增,可以用数组长度推导的方式,同时确保类型约束:
// 生成指定长度的数组类型 type ArrayWithLength<Len extends number, Arr extends any[] = []> = Arr["length"] extends Len ? Arr : ArrayWithLength<Len, [...Arr, any]>; // 实现通用的数字+1工具类型,确保返回值为number type PlusOne<T extends number> = [...ArrayWithLength<T>, any]["length"] extends infer L ? L extends number ? L : never : never; interface LocationBase< Level extends number = number, LevelName extends string = "location", Child = LocationBase<PlusOne<Level>> > { level: Level; levelName: LevelName; children?: Child[]; } // 定义目标层级类型 type Region = LocationBase<1, "region", Subregion>; type Subregion = LocationBase<2, "subregion", Area>; type Area = LocationBase<3, "area">;
关键修复点
- 解决循环默认值错误:将
Child的默认值从LocationBase改为LocationBase<PlusOne<Level>>,这样默认情况下会自动关联下一级的位置类型,避免直接引用自身导致的循环。 - 修复类型约束错误:确保
PlusOne<Level>的返回值被TypeScript识别为number类型,无论是通过条件判断覆盖常用范围,还是通过数组推导加类型断言的方式,都能满足Level extends number的约束。
内容的提问来源于stack exchange,提问作者GjfLeo
相关产品推荐
相关产品推荐

