TypeScript中能否将对象属性用作自身值的字符串字面量类型?
问题:如何在不传递完整泛型的情况下约束对象键与值的匹配?
需求与期望效果
需要创建一个对象,要求每个字符串字面量键必须和对应值对象中的value字段值完全一致。期望示例如下:
const parts: PartMap = { foo: { value: 'foo' } // 合法 bar: { value: 'cats' } // 不合法,键bar与value的cats不匹配 }
尝试代码与问题
参考相关方案后尝试了以下代码,但PartMap未能将字符串字面量传递给Part类型,导致报错:
type StringLiteral<T> = T extends string ? string extends T ? never : T : never; interface Part<T extends string> { value: StringLiteral<T> } interface PartMap { [index: string]: Part<typeof index> } const x: PartMap = { // ⚠️ Type 'string' is not assignable to type 'never'. foo: { value: 'foo' } }
解决方案
直接使用接口的索引签名无法捕获每个键的字面量类型,需要借助泛型映射类型+辅助推导函数实现,无需手动传递完整泛型参数:
简洁实现版本
// 定义泛型映射类型,每个键K对应value为K的对象 type PartMap<T extends string> = { [K in T]: { value: K }; }; // 辅助函数,让TypeScript自动推导泛型参数 const createPartMap = <T extends string>(map: PartMap<T>) => map; // 合法示例:键与value匹配,无报错 const validParts = createPartMap({ foo: { value: 'foo' }, test: { value: 'test' } }); // 不合法示例:Type '"cats"' is not assignable to type '"bar"',自动报错 const invalidParts = createPartMap({ bar: { value: 'cats' } });
保留原Part接口的版本
如果需要保留单独的Part接口定义,可以调整为:
type StringLiteral<T> = T extends string ? string extends T ? never : T : never; interface Part<T extends string> { value: StringLiteral<T>; } // 泛型PartMap,每个键K对应Part<K> type PartMap<T extends string> = { [K in T]: Part<K> }; // 辅助推导函数 const createPartMap = <T extends string>(map: PartMap<T>) => map; // 合法使用 const valid = createPartMap({ foo: { value: 'foo' } }); // 非法使用会触发类型错误 const invalid = createPartMap({ bar: { value: 'cats' } });
原理说明
- 泛型映射类型
{ [K in T]: ... }会遍历所有推导出来的键字面量K,将每个键的类型与对应value的类型绑定。 - 辅助函数
createPartMap让TypeScript自动推导泛型参数T,无需手动指定,从而实现“不用传递完整泛型”的要求。
内容的提问来源于stack exchange,提问作者jpschroeder
相关产品推荐
相关产品推荐

