【问题标题】:Typescript writing RequiredProps generic using conditional typesTypescript 使用条件类型编写RequiredProps 泛型
【发布时间】:2020-12-14 04:37:44
【问题描述】:

问题

我正在尝试定义一个通用的 RequiredProps<T, K>,以便我可以要求接口 T 上的键 K 不可为空。

例如:

interface User {
  id: string;
  phoneNumber?: string;
}
type UserWithPhoneNumber = RequiredProps<User, 'phoneNumber'>

应该产生一个类型 UserWithPhoneNumber 等价于 { id: string; phoneNumber: string }

解决方案

经过反复试验,我得到了一些工作:

type RequiredProps<T, K extends keyof T> = {
  [P in Exclude<keyof T, K>]: T[P];
} & {
  [P in K]-?: T[P];
};

这会产生一个等效类型 { name: string } &amp; { phoneNumber: string },但为了便于阅读,我希望它完全是 { id: string; phoneNumber: string }

查看lib.es5.d.ts 中的其他类型,我想我可以使用条件类型来修复它,但是以下方法不起作用:

type RequiredProps<T, K extends keyof T> = {
  [P in keyof T]: P extends K ? NonNullable<T[P]> : T[P];
};

为什么这不起作用,如何解决?

这是一个可以修改的 CodeSandbox: https://codesandbox.io/s/serene-meadow-j20w1?file=/src/index.ts

【问题讨论】:

    标签: typescript typescript-generics conditional-types


    【解决方案1】:

    要将对象类型的交集转换为单一类型,您可以在其属性上使用标识mapping

    type Id<T> = { [K in keyof T]: T[K] };
    

    你可以看到它的行为:

    type Test = Id<{ a: string } & { b: number } & { c: boolean }>;
    /* type Test = {
        a: string;
        b: number;
        c: boolean;
    } */
    

    然后你可以像这样让你的RequiredProps 类型别名:

    type RequiredProps<T, K extends keyof T> = 
      Id<Omit<T, K> & Required<Pick<T, K>>>;
    

    让我们验证它是否能满足您的需求:

    type UserWithPhoneNumber = RequiredProps<User, 'phoneNumber'>;
    /* type UserWithPhoneNumber = {
        id: string;
        phoneNumber: string;
    } */
    

    看起来不错。与进来的相比,出来的属性可能会重新排序,这根本不会影响类型兼容性。希望这没关系,因为我想不出什么可以确保它们以相同的顺序出现。


    关于Omit&lt;T, K&gt; &amp; Required&lt;Pick&lt;T, K&gt;&gt; 的一句话。从概念上讲,这与您所做的类似,只是我使用的是标准库提供的utility types

    但是有区别;您的{[P in Exclude&lt;keyof T, K&gt;]: T[P]} 不再是同态,因此不会保留来自T 的属性修饰符。有关同态映射类型如何保留属性修饰符的描述,请参见 microsoft/TypeScript#12563。所以如果你这样做:

    type U1 = RequiredPropsV1<User, "id">
    /* type U1 = {
        phoneNumber: string | undefined;
    } & {
        id: string;
    } */
    const u1: U1 = { id: "" }; // error! phoneNumber is required
    

    您会看到您的版本最终生成了所有必需的属性(但将undefined 留在未提及属性的域中)。大概你想要的东西更像:

    type U2 = RequiredProps<User, "id">
    /* type U2 = {
        phoneNumber?: string | undefined;
        id: string;
    } */
    const u2: U2 = { id: "" }; // okay
    

    未提及的属性的必要性不会改变。


    无论如何,希望对您有所帮助;祝你好运!

    Playground link

    【讨论】:

      猜你喜欢
      • 2021-10-03
      • 2019-05-30
      • 2020-11-10
      • 2021-12-06
      • 1970-01-01
      • 1970-01-01
      • 2018-06-25
      • 2023-03-24
      • 2022-12-03
      相关资源
      最近更新 更多