合并接口以结合其属性生成新类型

时间:2020-11-09 19:09:26

标签: typescript typescript-generics

我有一个对象Locale,该对象保存了我的应用的本地化版本。

export const Locale = {
    EN: "en",
    DE: "de",
    EL: "el",
    FR: "fr",
}

export const Locales = Object.values(Locale)

我还有一个代表API类型的对象,即Trip

export interface Trip {
    id: number,
    title:  string,
    description: string,
    featured: boolean,
}

Trip的某些字段是可翻译的(在这种情况下为标题,说明),这意味着在某些情况下,API会发送带有其语言环境的本地化字段。

例如:

{
  "id": 1,
  "title_el": "Greek Title",
  "title_de": "German title",
  "title_fr": "French Title",
  "title_en": "English Title,
  "description_en": "",
  "description_el": "",
  "description_de": "",
  "description_fr": "",
  "featured": true
}

我想做的是以某种方式生成一种新类型LocalizedTrip,其中包含可本地化的字段以及所有不可本地化的字段。

我尝试利用Typescript的4.1 Key Remaps,但运气不佳

我创建了一个codeandbox来开始聚会: https://codesandbox.io/s/xenodochial-frog-dr6de?file=/src/types.ts

export const Locale = {
  EN: "en",
  DE: "de",
  EL: "el",
  FR: "fr",
}

export const Locales = Object.values(Locale)


type LocalizedType<T> = {
  [K in keyof T as `[what here?]`]: () => T[K]
}

export interface Trip {
  id: number,
  title: string, //Maybe create another type for the localizable fields?
  description: string, 
  featured: boolean,
}


type LocalizedTrip = LocalizedType<Trip>

const test: LocalizedTrip = {

}

test.title_en
test.title_de

2 个答案:

答案 0 :(得分:4)

我认为您需要明确哪些键应该本地化。我的默认猜测是那些键不是symbol且其值可分配给string的属性:

type LocalizableProps<T> = Exclude<{
    [K in keyof T]-?: T[K] extends string ? K : never }[keyof T]
    , symbol>;

Trip生成此代码:

type TripStringProps = LocalizableProps<Trip>
// type TripStringProps = "title" | "description"

如果这不能捕获您的意图,则可以将LocalizableProps更改为可以(或变得足够接近)的其他类型的函数。


具有与语言环境字符串的并集相对应的类型也有帮助。目前,Locale的属性仅被视为string,对于我们的目的而言太宽了。为了不丢失Locale对象中特定的string literal值,我们应该使用类似const assertion的东西:

export const Locale = {
    EN: "en", 
    DE: "de",
    EL: "el",
    FR: "fr",
} as const;

type Locales = typeof Locale[keyof typeof Locale]
/* type Locales = "en" | "de" | "el" | "fr" */

现在,如果您更改Locale对象,则编译器应注意并自动适应。


最后,我们可以定义LocalizedType<T, K>,其中K默认为LocalizableProps<T>,但是您可以在必要时覆盖它:

type LocalizedType<T, K extends Exclude<keyof T, symbol> = LocalizableProps<T>> = {
    [P in keyof T as  (
        P extends K ? `${P}_${Locales}` : P
    )]: T[P]
}

这是一个非常简单的键映射:对于P中的每个键keyof T,如果P是指定键K之一,则使用并集{{1 }},它成为一个并集(例如,如果`${P}_${Locales}`P,则"x"将为${P}_{Locales}。否则,保持不变。


让我们对其进行测试:

"x_en" | "x_de" | "x_el" | "x_fr"

看起来不错,让我们看看如果指定其他键会发生什么情况

type LocalizedTrip = LocalizedType<Trip>

/* type LocalizedTrip = {
    id: number;
    title_en: string;
    title_de: string;
    title_el: string;
    title_fr: string;
    description_en: string;
    description_de: string;
    description_el: string;
    description_fr: string;
    featured: boolean;
} */

Playground link to code

答案 1 :(得分:0)

不是最易读的类型,但这是可行的。需要TS4.1。

Playground Link

用法:

type LocalizedTrip = LocalizedType<Trip, "title" | "description">;
// type LocalizedTrip = {
//     title_en: string;
//     title_de: string;
//     title_el: string;
//     title_fr: string;
//     description_en: string;
//     description_de: string;
//     description_el: string;
//     description_fr: string;
//     id: number;
//     featured: boolean;
// }

定义:

type LocalizedType<T, K> = {
  [LK in keyof typeof Locale as 0]: {
    [TK in Extract<keyof T, string>
      as (TK extends K ? `${TK}_${(typeof Locale)[LK]}` : TK)]: T[TK]
  }
}[0];

0用作将内部对象组合在一起的外部键。 TK extends K ?三元可确保仅对K中指定的键进行本地化。


编辑:基于Jcalz的回答,我们可以摆脱0

type LocaleStr = (typeof Locale)[keyof typeof Locale];

type LocalizedType<T, K> = {
  [TK in Extract<keyof T, string>
    as (TK extends K ? `${TK}_${LocaleStr}` : TK)]: T[TK]
};