TypeScript中如何以类型为对象键?示例代码报错求解
TypeScript字体测量层级类型约束问题解决方案
问题背景
需要实现带严格层级类型约束的measureText函数,要求调用时指定的字重必须属于对应字体族,字号必须属于对应字重,同时解决原代码中的类型错误问题。
原代码核心错误
- 枚举
FontFamilyType的键与值类型不匹配,导致索引MEASUREMENTS时触发隐式any类型转换 - 泛型类型
W被当作值使用,违反TypeScript类型与值分离的规则 - 原
Record类型定义过于宽泛,丢失了MEASUREMENTS中具体的字重、字号字面量信息,削弱类型检查能力
修正后的完整实现
// 调整枚举值与MEASUREMENTS的键完全匹配 enum FontFamilyType { Matter = 'Matter', Inter = 'Inter', } // 用as const锁定字面量类型,保留所有层级的具体类型信息 const MEASUREMENTS = { [FontFamilyType.Matter]: { 600: { 36: { a: 10, b: 20 }, 30: { a: 8, b: 18 }, 24: { a: 6, b: 16 }, 18: { a: 5, b: 14 }, 16: { a: 4, b: 12 }, } }, [FontFamilyType.Inter]: { 700: { 36: { a: 11, b: 21 }, 24: { a: 7, b: 17 }, }, 600: { 20: { a: 6, b: 16 }, 18: { a: 5, b: 15 }, 15: { a: 4, b: 13 }, 13: { a: 3, b: 11 }, }, 400: { 20: { a: 5, b: 14 }, 18: { a: 4, b: 13 }, 15: { a: 3, b: 12 }, 13: { a: 2, b: 10 }, }, }, } as const; // 基于MEASUREMENTS的实际类型推导层级类型 type Measurements = typeof MEASUREMENTS; type WeightType<F extends FontFamilyType> = keyof Measurements[F]; type SizeType<F extends FontFamilyType, W extends WeightType<F>> = keyof Measurements[F][W]; function measureText< F extends FontFamilyType, W extends WeightType<F>, S extends SizeType<F, W> >(text: string): void { // 通过类型断言解决泛型类型到值的索引匹配问题 const sizeMeasurements = MEASUREMENTS[F as keyof Measurements][W as keyof Measurements[F]][S as keyof Measurements[F][W]]; console.log('测量数据:', sizeMeasurements); } // 合法调用:类型检查完全通过 measureText<FontFamilyType.Matter, 600, 30>('foo'); // 非法调用:类型检查报错(600不属于Inter的字重) // measureText<FontFamilyType.Inter, 600, 36>('bar');
关键修改说明
锁定字面量类型:
- 给
MEASUREMENTS添加as const,让TypeScript保留所有层级的具体字面量类型(如600、30等),而非自动推断为宽泛的string或number - 枚举值与
MEASUREMENTS的键完全对齐,避免索引时的类型不匹配
- 给
解决类型与值混用问题:
- 通过类型断言
F as keyof Measurements将泛型类型转换为可用于索引的键类型,绕过TypeScript的严格索引检查(因泛型仅在编译期存在,运行时无对应值)
- 通过类型断言
精确层级类型推导:
- 基于
MEASUREMENTS的实际类型自动推导WeightType和SizeType,确保字重必须属于指定字体族,字号必须属于指定字重,实现严格的层级约束
- 基于
更直观的替代实现(推荐)
如果允许将字体族、字重、字号作为函数参数传入,可避免类型断言,代码更直观:
const MEASUREMENTS = { Matter: { 600: { 36: { a: 10, b: 20 }, 30: { a: 8, b: 18 }, } }, Inter: { 700: { 36: { a: 11, b: 21 }, }, }, } as const; type FontFamily = keyof typeof MEASUREMENTS; type Weight<F extends FontFamily> = keyof typeof MEASUREMENTS[F]; type Size<F extends FontFamily, W extends Weight<F>> = keyof typeof MEASUREMENTS[F][W]; function measureText<F extends FontFamily, W extends Weight<F>, S extends Size<F, W>>( fontFamily: F, weight: W, size: S, text: string ): void { const sizeMeasurements = MEASUREMENTS[fontFamily][weight][size]; console.log('测量数据:', sizeMeasurements); } // 合法调用 measureText('Matter', 600, 30, 'foo');
内容的提问来源于stack exchange,提问作者Alien
相关产品推荐
相关产品推荐

