假设我有一个Resource接口,它有大约translations个. 这是我翻译的模型的"记录库". 我对"真实"记录也有类似的界面,比如产品:

interface Translation {
    locale: string
}

interface Resource<T extends Translation> {
    translations: T[]
}

interface ProductTranslation {
  locale: string,
  title: string,
  status: number
}

interface Product {
  translations: ProductTranslation[]
}

我想编写一个"生成器"函数,该函数将为给定的翻译属性返回一个getter,例如,在Vanilla JS中:

function getTranslationValue(locale, key) {
  return (resource) => {
    const translation = resource.translations.find(t => t.locale === locale)
    return (translation && translation[key]) || null
  }
}

我发现在TS中编写它的唯一方法需要声明2个泛型类型:

  • 资源类型(R<T>)
  • 资源转换类型(T extends Translation)

就像这样:

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 = generateGetTranslationValue<Product, Product['translations'][number]>('en', 'title')
getTitle(myProduct)

有没有一种方法可以go 掉Product['translations'][number]声明,让调用看起来像这样:

const getTitle = generateGetTranslationValue<Product>('en', 'title')
getTitle(myProduct)

推荐答案

您的函数在两个类型参数中确实不是generic,至少在概念上是这样.您有一个类型参数R,您希望在调用该函数时使用类型参数手动指定它,如getTranslationValue<Product>(⋯).你所说的TR['translations'][number]似乎是一样的,所以你可以直接使用它.

当然,没有T,你就不可能constrain RResource<T>.但幸运的是,由于Resource<T>T中的covariant(参见Difference between Variance, Covariance, Contravariance, Bivariance and Invariance in TypeScript),您可以将其限制为Resource<Translation>.这给了你

function getTranslationValue<R extends Resource<Translation>>(
  locale: R['translations'][number]['locale'],
  key: keyof R['translations'][number]
) {
  return (resource: R) => {
    const translation = resource.translations.find(t => t.locale === locale)
    // const translation: Translation | undefined, oops, 
    const value = translation && translation[key] // error
    // ------------------------> ~~~~~~~~~~~~~~~~
    // Type 'keyof R["translations"][number]' cannot be used to index type 'Translation'.
    return value ?? null
  }
}

不幸的是,这并不能直接起作用.TypeScrip决定resource.translations可以扩大到Translation[],这是准确的,但不足以满足您的需求.您确实希望它类似于R['translations'][number][],这样find()就会返回R['translations'][number] | undefined.你可以自己明确地 Select annotate个类型,而且它是有效的:

function getTranslationValue<R extends Resource<Translation>>(
  locale: R['translations'][number]['locale'],
  key: keyof R['translations'][number]
) {
  return (resource: R) => {
    const translations: R['translations'][number][] = resource.translations;
    const translation = translations.find(t => t.locale === locale)
    const value = translation && translation[key]
    return value ?? null
  }
}

const getTitle = getTranslationValue<Product>('en', 'title');
// const getTitle: (resource: Product) => NonNullable<string | number> | null

或者你可以重写,这样resource就像Resource<R['translations'][number]>R一样被视为类型,看起来像这样:

function getTranslationValue<R extends Resource<Translation>>(
  locale: R['translations'][number]['locale'],
  key: keyof R['translations'][number]
) {
  return (resource: Extract<R, Resource<R['translations'][number]>>) => {
    const translation = resource.translations.find(t => t.locale === locale)
    const value = translation && translation[key]
    return value ?? null
  }
}
const getTitle = getTranslationValue<Product>('en', 'title')
// const getTitle: (resource: Product) => NonNullable<string | number> | null

其中,Extract<R, Resource<R['translations'][number]>使用the Extract utility typeR过滤为Resource<R['translations'][number]>.从调用方的Angular 来看,它看起来就像R(因此是Product,而不是Resource<ProductTranslation>),但在函数内部,编译器将其视为可分配给两种类型.

Playground link to code

Typescript相关问答推荐

当使用`type B = A`时,B的类型显示为A.为什么当`type B = A时显示为`any `|用A代替?

如何将联合文字类型限制为文字类型之一

TypeScrip 5.3和5.4-对`Readonly<;[...number[],字符串]>;`的测试版处理:有什么变化?

在分配给类型的只读变量中维护const的类型

诡异的文字类型--S为物语的关键

Fp-ts使用任务的最佳方法包装选项

推断从其他类型派生的类型

如何在省略一个参数的情况下从函数类型中提取参数类型?

如何键入并类型的函数&S各属性

是否可以在类型脚本中定义条件返回类型,而不在函数体中使用类型转换?

可赋值给类型的类型,从不赋值

在TypeScript中,如何通过一系列过滤器缩小类型?

我可以将一个类型数组映射到TypeScrip中的另一个类型数组吗?

来自枚举的<;复选框和组件列表

在对象数组中设置值

将带有额外id属性的Rust struct 展平回TypeScript单个对象,以返回wasm-bindgen函数

Typescript泛型-键入对象时保持推理

如何让Record中的每个值都有独立的类型?

Typescript 和 Rust 中跨平台的位移数字不一致

const 使用条件类型时,通用类型参数不会推断为 const