TypeScript中如何从资源类型自动推导其翻译泛型类型?
简化TypeScript翻译属性获取器的泛型声明
问题背景
我定义了以下TypeScript接口,其中Translation是翻译项的基础接口,Resource作为带翻译的资源基类,Product是具体的资源类型:
interface Translation { locale: string } interface Resource<T extends Translation> { translations: T[] } interface ProductTranslation { locale: string, title: string, status: number } interface Product { translations: ProductTranslation[] }
我想要实现一个生成器函数,用来创建获取指定翻译属性的函数,原生JavaScript版本如下:
function getTranslationValue(locale, key) { return (resource) => { const translation = resource.translations.find(t => t.locale === locale) return (translation && translation[key]) || null } }
目前我在TypeScript中的实现需要声明两个泛型参数,调用时必须同时传入资源类型和对应的翻译类型,代码如下:
function getTranslationValue<R extends Resource<T>, T extends Translation>( locale: R['translations'][number]['locale'], key: keyof R['translations'][number] ) { return (resource: R) => { const translation = resource.translations.find(t => t.locale === locale) const value = translation && translation[key] return value ?? null } }
调用示例:
const getTitle = getTranslationValue<Product, Product['translations'][number]>('en', 'title') getTitle(myProduct)
我希望能省略Product['translations'][number]这个泛型参数,让调用简化为:
const getTitle = getTranslationValue<Product>('en', 'title') getTitle(myProduct)
解决方案
可以通过从资源类型R中自动推导翻译项类型,来省略第二个泛型参数。以下是两种可行的实现方式:
方式一:保留Translation约束的简化写法
function getTranslationValue<R extends { translations: T[] }, T extends Translation>( locale: T['locale'], key: Exclude<keyof T, 'locale'> // 可选:限制key为非locale的翻译属性 ) { return (resource: R) => { const translation = resource.translations.find(t => t.locale === locale) return translation?.[key] ?? null } }
方式二:直接从R提取翻译类型
如果不需要依赖Resource接口,也可以直接从资源类型中提取翻译项的结构:
function getTranslationValue<R extends { translations: Array<{ locale: string; [key: string]: unknown }> }>( locale: R['translations'][number]['locale'], key: Exclude<keyof R['translations'][number], 'locale'> ) { return (resource: R) => { const translation = resource.translations.find(t => t.locale === locale) return translation?.[key] ?? null } }
简化后的调用
现在只需要传入资源类型作为唯一泛型参数,TypeScript会自动推导翻译项的合法属性:
// 合法调用:key为ProductTranslation的title或status const getTitle = getTranslationValue<Product>('en', 'title') const getStatus = getTranslationValue<Product>('en', 'status') // 非法调用(会触发TS报错):locale不是有效的属性(如果用了Exclude约束) // const getLocale = getTranslationValue<Product>('en', 'locale') // 测试示例 const myProduct: Product = { translations: [ { locale: 'en', title: 'Apple', status: 1 }, { locale: 'zh', title: '苹果', status: 1 } ] } console.log(getTitle(myProduct)) // 输出:Apple console.log(getStatus(myProduct)) // 输出:1 console.log(getTranslationValue<Product>('fr', 'title')(myProduct)) // 输出:null
原理说明
- 仅保留
R作为泛型参数,约束R必须包含translations数组属性 - 通过
R['translations'][number]自动提取数组中单个翻译项的类型,无需显式声明T - 使用
Exclude<keyof ..., 'locale'>可以限制key只能是翻译项的业务属性,避免无意义的调用
内容的提问来源于stack exchange,提问作者lcoq
相关产品推荐
相关产品推荐

