如何使用映射类型确保嵌套控件键完全匹配且值为string?
问题描述
给定以下TypeScript类型与变量:
type Form = { controls: { [key: string]: any; } } const form: Form = { controls: { firstName: {}, lastName: {}, }, };
需求是创建一个类型,要求对象必须包含form.controls的所有键,且每个键对应的值类型为string,示例对象如下:
const formControlsWithStrings = { firstName: 'foo', lastName: 'bar', };
尝试了以下写法:
type FormControlsWithStrings<T extends Form> = { [key in keyof T['controls']] : string }
但用此类型标注formControlsWithStrings时,仅能强制值类型为string,无法保证键的完全匹配——既不要求包含所有必填键,还允许添加额外的未定义键。
原因分析
问题出在原始的Form类型定义上:controls字段使用了字符串索引签名([key: string]: any),这会告诉TypeScript,controls可以包含任意字符串作为键,值为任意类型。
此时keyof T['controls']的结果是string | number(因为对象的数字键会被自动转换为字符串),基于这个生成的FormControlsWithStrings本质上还是一个带有字符串索引的类型,等价于:
type FormControlsWithStrings = { [key: string]: string; }
自然无法约束必须包含特定键,也拦不住额外键的添加。
解决方案
要实现精确的键约束,核心是让TypeScript保留form.controls的具体字面量类型,而不是被索引签名覆盖。
步骤1:保留form的具体类型
首先去掉form的显式Form类型标注,让TypeScript自动推断其具体结构:
const form = { controls: { firstName: {}, lastName: {}, }, };
如果必须保留Form的类型校验,可以用类型断言结合as const来保留字面量信息:
const form = { controls: { firstName: {}, lastName: {}, }, } as const satisfies Form;
步骤2:生成精确类型
通过typeof获取form.controls的具体类型,再提取其键名来生成目标类型,有两种常用写法:
写法1:直接使用Record工具类型
type FormControlsWithStrings = Record<keyof typeof form.controls, string>;
Record<K, V>会生成一个包含所有K类型键、值类型为V的精确对象类型,自动约束必须包含所有键且不能添加额外键。
写法2:手动映射类型
type FormControlsWithStrings = { [K in keyof typeof form.controls]: string; };
效果和Record写法完全一致,手动遍历form.controls的所有键,指定值类型为string。
验证效果
使用生成的类型标注对象时,TypeScript会严格校验键的完整性和唯一性:
// ✅ 符合要求:包含所有键,值为string const formControlsWithStrings: FormControlsWithStrings = { firstName: 'foo', lastName: 'bar', }; // ❌ 错误:缺少lastName键 const missingKey: FormControlsWithStrings = { firstName: 'foo', }; // ❌ 错误:多出了age键 const extraKey: FormControlsWithStrings = { firstName: 'foo', lastName: 'bar', age: '20', };
内容的提问来源于stack exchange,提问作者wlf

