如何为带索引签名的对象类型关联内层与顶层属性?
如何在顶层属性未知时约束对象的嵌套属性?
先看一个已知可行的场景:当顶层属性是固定联合类型时,我们可以通过遍历联合成员来约束子属性的前缀:
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type Obj = { [K in "prop_a" | "prop_b" | "prop_c"]: { [P in `${K}${Digit}`]: unknown; } }
这段代码的效果是:每个顶层属性(比如prop_a)的子对象,只能包含prop_a0到prop_a9这些属性,前缀和顶层键严格对应。
问题
如果顶层属性的名称是未知的(不是固定的联合类型),能不能实现同样的约束?也就是不管用户定义什么顶层键,对应的子属性必须是「顶层键+0-9数字」的格式?
错误的尝试
直接用string作为索引签名的写法无法实现约束:
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; type Obj = { [K in string]: { [P in `${K}${Digit}`]: unknown; } }
这种写法下,内层属性的前缀和顶层键没有关联,不符合预期的代码不会报错:
declare const obj: Obj; // 本应报错(前缀不是"whatever"),但当前类型定义下不会 obj.whatever.it_should_error0; // 只有这些才是符合预期的合法属性 obj.whatever.whatever0; obj.whatever.whatever1; // ... 直到whatever9
正确的解决方案
需要用泛型来关联顶层键和子属性的前缀,具体实现如下:
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9; // 泛型类型,T代表所有顶层键的类型 type ConstrainedObj<T extends string> = { [K in T]: { [P in `${K}${Digit}`]: unknown; } }; // 用法1:明确指定顶层键类型 type SpecificObj = ConstrainedObj<"foo" | "bar">; declare const specificObj: SpecificObj; specificObj.foo.foo0; // 合法 specificObj.foo.bar0; // 报错,符合预期 // 用法2:通过泛型函数自动推断顶层键 function createObj<T extends string>(obj: ConstrainedObj<T>): ConstrainedObj<T> { return obj; } // 传入对象时,TypeScript会自动约束子属性必须匹配顶层键 const myObj = createObj({ whatever: { whatever0: null, whatever5: "hello", // it_should_error0: 123, // 取消注释会报错,符合预期 }, anotherKey: { anotherKey9: true, } });
原理说明
泛型T会捕获所有顶层键的具体类型,当遍历K in T时,每个K都是当前顶层键的具体值,因此子属性P in ${K}${Digit}``会被严格约束为「当前顶层键+数字」的格式,完美实现了顶层属性未知时的关联约束。
内容的提问来源于stack exchange,提问作者Andrea Simone Costa
相关产品推荐
相关产品推荐

