【问题标题】:Reverse required and optional properties反转必需和可选属性
【发布时间】:2019-08-21 22:07:08
【问题描述】:

假设我有以下类型:

interface Foo {
  a?: string;
  b: string;
}

所有属性都可以使用Required 来设置。

type Bar = Required<Foo>;

// Equivalent to
interface Bar {
  a: string;
  b: string;
}

所有属性都可以使用Partial 设为可选。

type Bar = Partial<Foo>;

// Equivalent to
interface Bar {
  a?: string;
  b?: string;
}

我想创建一个泛型类型而不是翻转所有属性的可选标志。

type Bar = Flip<Foo>;

// Equivalent to
interface Bar {
  a: string;
  b?: string;
}

PartialRequired 的实现非常简单,但是这个Flip 类型需要知道Foo 的哪些属性是可选的,哪些是必需的。是否可以使用泛型读取此修改?

一些上下文:我相信这对于 React 中的 defaultProps 来说是一个有用的类型。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    对于您所说的问题(Foo 没有index signature),您可以像这样定义Flip(我称之为FlipOptional):

    type OptionalKeys<T> = {
      [K in keyof T]-?: {} extends Pick<T, K> ? K : never
    }[keyof T];
    
    type FlipOptional<T> = (Required<Pick<T, OptionalKeys<T>>> &
      Partial<Omit<T, OptionalKeys<T>>>) extends infer O
      ? { [K in keyof O]: O[K] }
      : never;
    

    首先我们必须确定T 的哪些键是可选的。没有简单的内置方法可以做到这一点,但上面的OptionalKeys 类型别名通过检查T 的哪些单属性扩展是weak types 来工作,如answer to a related question 中所述。索引签名搞砸了,所以如果您需要对具有索引签名的类型进行操作,您将需要更复杂的东西(在链接的答案中也有描述)。

    一旦您确定了哪些键是可选的,就可以相对简单地构造一个与您想要的等效类型,PickOmit 以及 PartialRequired 以及一个交集:Required&lt;Pick&lt;T, OptionalKeys&lt;T&gt;&gt;&gt; &amp; Partial&lt;Omit&lt;T, OptionalKeys&lt;T&gt;&gt;&gt; .唯一的问题是这种类型比较难看;如果将类型写为单个对象类型会更好。

    这里我使用conditional type inference 的以下形式的技巧。假设我们有一个类型Ugly,它由许多交叉点和映射组成。为了让它漂亮,我们可以这样做

    type Pretty = Ugly extends infer O ? {[K in keyof O]: O[K]} : never
    

    这里,Ugly 本质上是“复制”到O 参数,该参数被映射而不更改任何内容。就输出而言,这主要是无操作,但如果可能,结果将是单个对象类型。


    让我们试试吧:

    interface Foo {
      a?: string;
      b: string;
    }
    
    type Bar = FlipOptional<Foo>;
    /* type Bar = {
        a: string;
        b?: string | undefined;
    } */
    
    type FooAgain = FlipOptional<Bar>;
    /* type FooAgain =  {
        b: string;
        a?: string | undefined;
    } */
    
    type MutuallyAssignable<T extends U, U extends V, V = T> = true;
    type FooAgainIsFoo = MutuallyAssignable<Foo, FooAgain>; // okay
    

    这一切看起来都不错。 Bar 是你所期望的,FlipOptional&lt;Bar&gt; 给出了一个等价于Foo 的类型。


    好的,希望对您有所帮助。祝你好运!

    Link to code

    【讨论】:

      【解决方案2】:

      你可以使用这样的类型系统来构造Flip&lt;T&gt;

      type OptionalPropertyNames<T> = {
          [K in keyof T]-?: undefined extends T[K] ? K : never
      }[keyof T];
      type RequiredPropertyNames<T> = {
          [K in keyof T]-?: undefined extends T[K] ? never : K
      }[keyof T];
      type OptionalProperties<T> = Pick<T, OptionalPropertyNames<T>>
      type RequiredProperties<T> = Pick<T, RequiredPropertyNames<T>>
      
      type Flip<T> = Partial<RequiredProperties<T>> & Required<OptionalProperties<T>>;
      
      
      type Bar = Flip<X>;
      
      // ==
      interface Bar {
        a: string;
        b?: string | undefined;
      }
      
      

      this playground

      如果您可以让Flip 生成一个没有可选属性的类型,而是将值与undefined 联合,您可以这样定义:

      type Flip<X> = {
          [K in keyof X]-?: undefined extends X[K] ? X[K] : X[K] | undefined;
      };
      

      在您的示例中生成以下类型:

      type Bar = Flip<Foo>;
      
      // Equivalent to
      interface Bar {
        a: string;
        b: string | undefined;
      }
      

      这在大多数情况下应该足够了,并且提供了比上面的示例更好的自动完成和“更干净”的类型。

      解释:

      [K in keyof X]-?:
      

      这使得每个属性都是必需的。下一位(条件类型)检查该类型是否是必需的,并在这种情况下创建与 undefined 的联合 - 使其实际上是可选的:

      X[K] extends undefined ? X[K] : X[K] | undefined;
      

      【讨论】:

      猜你喜欢
      • 2013-12-27
      • 2016-08-29
      • 2014-12-25
      • 2018-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多