TypeScript:创建以维度为泛型的坐标类型及类型适配
TypeScript 泛型实战:特定长度数组的接口与函数适配
需求与初始代码
我需要定义几种特定长度的数组类型,包含固定长度数组和最小长度数组,初始代码如下:
interface FixedLengthArray<T, L extends number> extends ReadonlyArray<T> { 0: T; length: L; } type Coordinates2D = FixedLengthArray<number, 2>; type Coordinates3D = FixedLengthArray<number, 3> type CoordinatesMin2D = { 0: number 1: number } & ReadonlyArray<number> type CoordinatesMin3D = { 0: number 1: number 2: number } & ReadonlyArray<number> type Coordinates = ReadonlyArray<number>; // 修正原代码语法错误
接下来要解决两个核心问题:
- 定义能兼容上述所有坐标类型的
KeyAndCoords接口; - 实现
center2DCoordinates函数,仅允许传入包含至少2维坐标的KeyAndCoords(即Coordinates2D/Coordinates3D/CoordinatesMin2D/CoordinatesMin3D,排除普通Coordinates类型,因为它可能长度不足2)。
补充规则:
- 普通
Coordinates为任意长度数组,不能直接传入center2DCoordinates,但getMaxOfCoordinates这类函数需要兼容所有坐标类型; - 以下传入
center2DCoordinates的场景需符合预期:x: Coordinates = [1] // 失败(长度不足2) x: Coordinates = [1, 1] // 成功(长度为2) x: Coordinates = [1, 1, 1] // 成功(长度≥2) x: Coordinates2D = [1, 1] // 成功 x: Coordinates3D = [1, 1, 1] // 成功 x: CoordinatesMin2D = [1, 1, 1] // 成功 center2DCoordinates([{ key: "test", coordinates: x }])
解决方案
1. 优化最小长度数组的泛型定义
封装通用的最小长度数组泛型,避免重复定义冗余类型:
type MinLengthArray<T, L extends number> = L extends 0 ? ReadonlyArray<T> : { [K in 0..L-1]: T } & ReadonlyArray<T>; // 用泛型重新定义最小长度坐标类型 type CoordinatesMin2D = MinLengthArray<number, 2>; type CoordinatesMin3D = MinLengthArray<number, 3>;
2. 定义泛型版KeyAndCoords接口
让接口支持泛型参数,兼容所有坐标类型:
interface KeyAndCoords<C extends ReadonlyArray<number>> { key: string; coordinates: C; }
3. 实现center2DCoordinates的类型约束
通过条件类型判断坐标是否满足至少2个元素,同时用泛型约束函数参数:
// 类型守卫:判断坐标类型是否至少包含2个元素 type IsAtLeast2D<C extends ReadonlyArray<number>> = C extends { length: infer L } ? L extends number ? L extends 0 | 1 ? false : true : true : C extends { 0: number; 1: number } ? true : false; // 函数仅接收满足IsAtLeast2D的坐标类型 const center2DCoordinates = <C extends ReadonlyArray<number>>( keyAndCoords: KeyAndCoords<C>[] ): KeyAndCoords<C> => { // 业务逻辑示例:计算中心坐标 const totalX = keyAndCoords.reduce((sum, item) => sum + item.coordinates[0], 0); const totalY = keyAndCoords.reduce((sum, item) => sum + item.coordinates[1], 0); return { key: "center", coordinates: [totalX / keyAndCoords.length, totalY / keyAndCoords.length] as C }; }; // 兼容所有坐标类型的示例函数 const getMaxOfCoordinates = <C extends ReadonlyArray<number>>( keyAndCoords: KeyAndCoords<C>[] ): number => { return Math.max(...keyAndCoords.flatMap(item => item.coordinates)); };
验证传入场景
测试各种传入情况,符合预期约束:
// 失败:Coordinates长度为1,不满足要求 const invalidCoord: Coordinates = [1]; center2DCoordinates([{ key: "test", coordinates: invalidCoord }]); // 类型错误 // 成功:Coordinates长度为2 const validCoord2: Coordinates = [1, 1]; center2DCoordinates([{ key: "test", coordinates: validCoord2 }]); // 正常通过 // 成功:Coordinates长度为3 const validCoord3: Coordinates = [1, 1, 1]; center2DCoordinates([{ key: "test", coordinates: validCoord3 }]); // 正常通过 // 成功:固定长度2D坐标 const coord2D: Coordinates2D = [1, 1]; center2DCoordinates([{ key: "test", coordinates: coord2D }]); // 正常通过 // 成功:固定长度3D坐标 const coord3D: Coordinates3D = [1, 1, 1]; center2DCoordinates([{ key: "test", coordinates: coord3D }]); // 正常通过 // 成功:最小长度2D坐标 const minCoord2D: CoordinatesMin2D = [1, 1, 1]; center2DCoordinates([{ key: "test", coordinates: minCoord2D }]); // 正常通过
内容的提问来源于stack exchange,提问作者theahura
相关产品推荐
相关产品推荐

