TypeScript中对Record<string,string>部分键的类型安全约束
问题描述
假设我有一个COUNTRIES常量数组:
const COUNTRIES = [ "fr", "sp", "de", "uk" ] as const; type CountryCode = typeof COUNTRIES[number];
另外,我有一个存储应用翻译内容的大型Record<string,string>映射:
const EN_TRANSLATIONS: Record<TranslationKeys, string> = { "hello-world": "Hello world !", "what-time": "What time is it ?", "countries.fr": "France", "countries.sp": "Spain", "countries.de": "Germany", "countries.uk": "United Kingdom" }
能否定义上述TranslationKeys类型,在编译时强制执行以下规则:
- 可使用任意字符串作为键,无需在联合类型中白名单所有键(不想维护专门的键联合类型)
- 存在遵循特定约定的“特殊”键并附带类型约束:示例中
countries.*键需包含COUNTRIES的所有条目(匹配CountryCode联合类型),若缺失则编译器报错(新增国家代码时不会忘记添加翻译) - 尽量保持
EN_TRANSLATIONS的现有写法(仅修改TranslationKeys类型):已知一种方案是拆分变量并使用扩展运算符,但希望避免这种方式。
我尝试过TypeScript 4.9新增的satisfies关键字,但效果不佳。请问有什么可行的解决方法?
解决方案
要实现需求,我们可以通过交叉类型结合模板字面量类型和条件约束来定义TranslationKeys,直接通过TypeScript的类型检查机制强制特殊键的完整性,同时保留原有写法。
具体实现代码
const COUNTRIES = [ "fr", "sp", "de", "uk" ] as const; type CountryCode = typeof COUNTRIES[number]; // 生成所有必填的国家翻译键,格式为countries.xx type RequiredCountryKeys = { [K in CountryCode as `countries.${K}`]: string; }; // 定义TranslationKeys:允许任意字符串键,同时强制包含所有RequiredCountryKeys的键 type TranslationKeys = string & keyof RequiredCountryKeys; // 测试验证: // 1. 可以添加任意自定义键(比如下面的"greeting") // 2. 缺少某个国家键会直接触发编译错误 const EN_TRANSLATIONS: Record<TranslationKeys, string> = { "hello-world": "Hello world !", "what-time": "What time is it ?", "greeting": "Hi there!", // 自定义键,完全允许 "countries.fr": "France", "countries.sp": "Spain", "countries.de": "Germany", // 注释掉下面这行会报错:对象字面量缺少属性"countries.uk" "countries.uk": "United Kingdom" };
原理说明
- RequiredCountryKeys:借助模板字面量类型,自动生成所有
countries.${CountryCode}格式的键,并将它们标记为必选的字符串值类型。当COUNTRIES数组新增国家代码时,这个类型会自动同步更新。 - TranslationKeys:使用
string & keyof RequiredCountryKeys的交叉类型,既保留了“允许任意字符串作为键”的灵活性,又强制要求对象必须包含所有keyof RequiredCountryKeys对应的键——TypeScript在检查Record<TranslationKeys, string>类型时,会同时验证这两个条件。 - 避免拆分变量:整个实现只需要修改
TranslationKeys类型,完全不需要拆分EN_TRANSLATIONS变量或使用扩展运算符,完美符合你希望保留原有写法的要求。
为什么satisfies效果不好?
satisfies关键字仅做类型兼容性校验,不会强制要求对象包含某个类型的所有必选键。而上面的方案通过将必选约束嵌入到TranslationKeys类型中,直接利用Record的类型检查机制来强制完整性,刚好解决了satisfies的局限性。
内容的提问来源于stack exchange,提问作者Frédéric Camblor
相关产品推荐
相关产品推荐

