TypeScript实现指向Translation类型的点分隔路径强类型校验
TypeScript 翻译路径强类型校验实现方案
报错原因
你遇到的类型报错是 TypeScript 泛型约束的前置校验逻辑导致的:即使你已经在条件分支中写了T[TKey] extends Translations的判断,TS 在校验递归泛型参数时不会提前带入条件分支的判断结果,依然会用T[TKey]的原始类型Translation | Translations | undefined去匹配Translations约束,因此触发报错。
最优实现方案
我们可以换个思路:直接从翻译结构中生成所有合法的、指向Translation类型的点分隔路径联合类型,再用这个联合类型约束入参,逻辑更简洁,也能避开递归参数校验问题:
type Language = "pl" | "en"; type Translation = { [lang in Language]: string }; type Translations = { [key: string]: Translation | Translations | undefined }; // 核心:生成T下所有指向Translation类型的点分隔路径联合 type PathsToTranslation<T extends Translations> = { // 遍历所有字符串类型的key [K in keyof T & string]: T[K] extends Translation // 如果当前key对应的值就是Translation,直接返回当前key作为合法路径 ? K // 如果当前key对应的值还是Translations嵌套结构,递归拼接子路径 : T[K] extends Translations ? `${K}.${PathsToTranslation<T[K]>}` // 过滤undefined和其他不合法值 : never }[keyof T & string] // 函数实现 function printTranslationByDeepKey<T extends Translations, K extends PathsToTranslation<T>>( t: T, k: K ) { // 运行时按路径取值,类型已经保证取值结果为Translation const translation = k.split('.').reduce((current, key) => current?.[key], t) as Translation console.log(translation) }
测试效果
使用你提供的示例翻译结构测试,完全符合预期校验规则:
const translations = { hello: { pl: "Dzieńdobry", en: "Hello" }, bool: { yes: { pl: "Tak", en: "Yes" }, no: { pl: "Nie", en: "No" }, }, } as const; // 合法调用,无报错 printTranslationByDeepKey(translations, "hello") printTranslationByDeepKey(translations, "bool.yes") printTranslationByDeepKey(translations, "bool.no") // 非法调用,均触发类型报错 printTranslationByDeepKey(translations, "bool") // 指向嵌套结构,不合法 printTranslationByDeepKey(translations, "missing") // 不存在的路径 printTranslationByDeepKey(translations, "bool.foo") // 不存在的子路径 printTranslationByDeepKey(translations, "hello.pl") // 指向Translation内部字段,不合法
原有写法的修复方案
如果你要保留原有传入K做匹配的写法,只需要用Extract工具类型提取T[TKey]中符合Translations约束的部分,即可解决泛型参数校验报错:
type DeepKeyToTranslation<T extends Translations, K extends string> = K extends keyof T ? T[K] extends Translation ? K : never : K extends `${infer TKey}.${infer Rest}` ? TKey extends keyof T ? T[TKey] extends Translations // 用Extract提取合法的Translations类型传入递归 ? Rest extends DeepKeyToTranslation<Extract<T[TKey], Translations>, Rest> ? K : never : never : never : never
内容的提问来源于stack exchange,提问作者Chris Pfohl
相关产品推荐
相关产品推荐

