如何确保TypeScript映射类型的键值一致并支持递归嵌套?
解决TypeScript映射类型的严格约束与递归嵌套问题
一、基础问题:严格约束键与值完全相同
你最初定义的{ [Key in string]: Key }之所以无法约束键值一致,是因为TypeScript的结构类型检查会将字符串字面量视为string的子类型——只要值是string类型就会通过检查,而非严格匹配键的字面量。
要实现键与值完全相同的约束,需要让泛型T满足「每个键K的值必须等于K本身的字面量类型」,可以通过泛型自引用约束实现:
function mapped<const T extends { [K in keyof T]: K }>(t: T): T { return t; } // 合法:值与键严格匹配 const mapped1 = mapped({ 'foo': 'foo', }); // 报错:类型 '"bar"' 不能赋值给类型 '"foo"' const mapped2 = mapped({ 'foo': 'bar', });
二、递归嵌套结构的类型约束
针对你需要的递归嵌套结构,我们可以定义一个递归索引类型,确保每个层级的fn参数自动绑定当前键的字面量类型,同时children复用同类型实现无限嵌套:
type RecursiveMapped = { [Key in string]: { fn: (key: Key) => void; children: RecursiveMapped; }; };
如果需要严格约束传入对象的结构,同时自动推断每个fn的参数类型,可以结合泛型函数使用:
function createMapped<const T extends RecursiveMapped>(t: T): T { return t; } // 合法:每个fn的参数自动推断为对应键的字面量类型 const validTree = createMapped({ 'foo': { fn: (key) => { // key 类型为 'foo' console.log(key); }, children: { 'bar': { fn: (key) => { // key 类型为 'bar' console.log(key); }, children: {} } } } }); // 报错:fn的参数类型与键不匹配 const invalidTree = createMapped({ 'foo': { fn: (key: 'bar') => {}, // 类型 '"bar"' 不能赋值给类型 '"foo"' children: {} } });
若需要禁止对象包含额外属性,可以自定义Exact类型增强约束(注意递归场景下可能存在性能损耗,按需使用):
type Exact<T, U> = T extends U ? U extends T ? T : never : never; function createMapped<const T extends Exact<T, RecursiveMapped>>(t: T): T { return t; }
内容的提问来源于stack exchange,提问作者Lehks
相关产品推荐
相关产品推荐

