我使用的是TypeScript 4.9.5,我需要利用类型重新映射.在重新映射过程中,我希望保持optional property mode.具体地说,我需要将接口的每个属性转换为add a "child" prefix,并将属性的原始文本类型转换为大写.以下是我的接口定义:

interface ChildrenProps {
    children: ReactNode;
    className?: string;
    style?: CSSProperties;
}

我希望将其转换为以下形式:

interface ChildrenPropsWithPrefix {
    childChildren: ReactNode;
    childClassName?: string;
    childStyle?: CSSProperties;
}

为了实现这一点,我编写了以下类型实用程序:

/** Make all properties with a prefix and convert the first character of properties to uppercase */
type PrefixAll<T, P extends string> = {
    [K in keyof T & string as `${P}${Capitalize<K>}`]: T[K];
}

然而,我得到的结果如下:

interface ChildrenPropsWithPrefix {
    childChildren: ReactNode;
    childClassName: string;
    childStyle: CSSProperties;
}

正如你可能注意到的,这个结果和我预期的结果是不同的.所有的属性都变成了required,TS Playground link,而我希望它们变成remain optional.

虽然我可以通过设置ParentProps extend Partial\<PrefixAll\<ChildrenProps, 'child'\>\>来解决这个问题,但它改变了界面的原始 struct ,这可能会让其他开发人员感到困惑.我想要避免这样的情况.

你能建议一个解决方案或替代方法,让我在保持可选物业模式的同时达到预期的结果吗?如果您能提供任何代码示例或见解,我将不胜感激.

推荐答案

您希望PrefixAll<T, P>T的属性的homomorphic mapped type,这意味着它保留optionalreadonly属性修饰符(有关详细信息,请参见What does "homomorphic mapped type" mean?).这样做的标准方法是编写{[K in keyof T]: ⋯},其中您要映射的键是keyof一个泛型类型参数.这也适用于remapped types via as.

你的问题是,你写了{[K in keyof T & string]: ⋯},intersection中断了与T的连接,因为(keyof T) & string不是keyof,什么都不是.

如果将keyof T & string更改为keyof T,则生成的映射类型将根据需要是同态的.但这样你就不能使用Captialize<K>,因为K可能不是string.幸运的是,您可以只写Captialize<K & string>.当K & string的计算结果为never时,它具有完全取消该属性的效果,就像您已经编写了K in keyof T & string一样.因此,解决办法是将第& string条从第in条移至第as条:

type PrefixAll<T, P extends string> = {
  [K in keyof T as `${P}${Capitalize<K & string>}`]: T[K];
}

让我们来测试一下:

interface ChildrenProps {
  children: ReactNode;
  className?: string;
  style?: CSSProperties;
}

type ChildrenPropsWithPrefix = PrefixAll<ChildrenProps, "child">;
/* type ChildrenPropsWithPrefix = {
    childChildren: ReactNode;
    childClassName?: string;
    childStyle?: CSSProperties;
} */

看上go 不错!

Playground link to code

Typescript相关问答推荐

如何从TypScript中的接口中正确获取特定键类型的所有属性?

Angular 17 -如何使用新的@if语法在对象中使用Deliverc值

类型脚本类型字段组合

打印脚本丢失函数的泛型上下文

Angular 信号:当输入信号改变值时,S触发取数的正确方式是什么?

向NextAuth会话添加除名称、图像和ID之外的更多详细信息

等待用户在Angular 函数中输入

为什么我的一个合成函数不能推断类型?

Cypress页面对象模型模式.扩展Elements属性

Angular文件上传到Spring Boot失败,多部分边界拒绝

如果数组使用Reaction-Hook-Form更改,则重新呈现表单

如何将定义模型(在Vue 3.4中)用于输入以外的其他用途

转换器不需要的类型交集

->;Boolean类型的键不能分配给类型Never

错误TS7030:并非所有代码路径都返回值.";由tsfig.json中的";Strong";:False引起

接口中可选嵌套属性的类型判断

记录的子类型<;字符串,X>;没有索引签名

如何创建允许使用带有联合类型参数的Array.from()的TS函数?

如何在Vuetify 3中导入TypeScript类型?

Typescript 编译错误:TS2339:类型string上不存在属性props