在TypeScrip中,我想创建一个泛型,我可以使用它将带有特定后缀的PropertyKeys添加到现有类型中.每个后缀也应该包含一个值的类型.以下代码说明了目标是什么:

// Example how to use
type OriginalType = {
  name?: string;
  age?: number;
  // any other property
};

type ModifiedType = AddPostfixes<
  OriginalType,
  { Postfix1: number; Postfix2: string; Postfix3: boolean } // here the Postfixes with their related type should be defined
>;

// current implementation
type AddPostfixes<T, Postfixes extends Record<string, any>> = {
  [K in keyof T as `${K & string}${string &
    keyof Postfixes}`]: K extends `${string & infer Prefix}${string &
    keyof Postfixes}`
    ? Prefix extends keyof Postfixes
      ? Postfixes[keyof Postfixes]
      : never
    : K extends keyof T
    ? T[K]
    : Postfixes[string & keyof Postfixes];
} & T;


// expected/prefered type for ModifiedType in this case
type ModifiedTypeExpected = {
  namePostfix1?: number;
  namePostfix2?: string;
  namePostfix3?: boolean;
  agePostfix1?: number;
  agePostfix2?: string;
  agePostfix3?: boolean;
} & OriginalType;

// actual type i have now for ModifiedType in this case
type ModifiedTypeActual = {
  name?: string;
  age?: number;
  namePostfix1?: string;
  namePostfix2?: string;
  namePostfix3?: string;
  agePostfix1?: number;
  agePostfix2?: number;
  agePostfix3?: number;
};

const m: ModifiedType = {};
export {};

附加注意:当基键中的前缀为可选时,添加的前缀必须是可选的. 因此,即

type MyOriginalType = {
name: string;
age?: age;
}

// agePostfix1, agePostfix2, agePostfix3 has to be optional too
type PostfixedType = AddPostfixes<MyOriginalType>;


生成的后缀键也应该是可选的.

感谢您对修复泛型类型AddPostfixes的任何帮助.

推荐答案

这里有一个方法:

type AddPostfixes<T extends object, P extends object> = T & ({ [KP in keyof P]:
    (x: { [KT in keyof T as `${Exclude<KT, symbol>}${Exclude<KP, symbol>}`]: P[KP]
    }) => void } extends Record<string, (x: infer I) => void> ? I : never)

您要求第一个类型T参数中的optional properties映射到输出中的可选后缀属性,这会使问题变得更加复杂.为了保持可选性,您想要写一个homomorphic mapped type(参见What does "homomorphic mapped type" mean?),这意味着在答案中的某个地方,您将看到类似{[KT in keyof T]: ⋯}的内容,尽管对于属性值类型本身来说,这并不是必需的.无论如何,它的工作原理是这样的:

我们在原始T对象类型和后缀P对象类型上创建映射类型,以便我们迭代T的每个属性键KT和每个属性键KP.然后,我们基本上希望输出有一个类型为`${KT}${KP}`的键,值为类型P[KP].这说起来容易做起来难.

首先,我们必须确保键可以序列化为template literal types,需要Exclude<KT, symbol>KT & stringKT & (string | number)这样的值

然后,嵌套的映射类型最终会提供类似{Postfix1: {namePostfix1?: number, ⋯}, Postfix2: {namePostfix2?: number ⋯}, ⋯}的内容,但您只希望将这些内部属性组合成一个类型.这需要生成映射类型所有属性中的intersection个.要以编程方式做到这一点,需要进行一些类型调整;在上面的方法中,我使用了一种类似于Transform union type to intersection type的方法,将所有片段放在contravariant类型的位置,然后将infer from that放入交叉点.

基本上就是这样.是的,我们需要将整件事与T相交.所以,这就得出了这个结果:

type OriginalType = {
    name?: string;
    age?: number;
    otherProp: Date;
};

type ModifiedType = AddPostfixes<
    OriginalType,
    { Postfix1: number; Postfix2: string; Postfix3: boolean }
>;    

/* type ModifiedType = OriginalType & {
    namePostfix1?: number | undefined;
    agePostfix1?: number | undefined;
    otherPropPostfix1: number;
} & {
    namePostfix2?: string | undefined;
    agePostfix2?: string | undefined;
    otherPropPostfix2: string;
} & {
    namePostfix3?: boolean | undefined;
    agePostfix3?: boolean | undefined;
    otherPropPostfix3: boolean;
} */

这就是你想要的,主要是. 可以通过另一个映射类型将其折叠为单个对象类型:

type APF<T extends object, P extends object> =
    { [K in keyof AddPostfixes<T, P>]: AddPostfixes<T, P>[K] };
type MT = APF<OriginalType, { Postfix1: number; Postfix2: string; Postfix3: boolean }>;
/* type MT = {
    name?: string | undefined;
    age?: number | undefined;
    otherProp: Date;
    namePostfix1?: number | undefined;
    agePostfix1?: number | undefined;
    otherPropPostfix1: number;
    namePostfix2?: string | undefined;
    agePostfix2?: string | undefined;
    otherPropPostfix2: string;
    namePostfix3?: boolean | undefined;
    agePostfix3?: boolean | undefined;
    otherPropPostfix3: boolean;
} */

但我不知道这是否真的重要.

Playground link to code

Typescript相关问答推荐

如何根据参数的值缩小函数内的签名范围?

更新:Typescript实用程序函数合并对象键路径

TypScript手册中的never[]参数类型是什么意思?

带有联合参数的静态大小数组

处理API响应中的日期对象

在类型内部使用泛型类型时,不能使用其他字符串索引

typescribe警告,explate—deps,useEffect依赖项列表

未在DIST中正确编译TypeScrip src模块导入

是否可以针对联合类型验证类型属性名称?

如何实现异构数组内函数的类型缩小

路由链接始终返回到

如何以Angular 导入其他组件S配置文件

(TypeScript)TypeError:无法读取未定义的属性(正在读取映射)"

为什么Typescript只在调用方提供显式泛型类型参数时推断此函数的返回类型?

如何避免从引用<;字符串&>到引用<;字符串|未定义&>;的不安全赋值?

接受字符串或数字的排序函数

两个名称不同的相同打字界面-如何使其干燥/避免重复?

如何将对象的字符串文字属性用作同一对象中的键类型

如何在TypeScript中声明逆变函数成员?

使用 Next.js 路由重定向到原始 URL 不起作用