反转必需和可选属性

时间:2019-08-21 13:39:40

标签: typescript

假设我具有以下类型:

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是有用的类型。

2 个答案:

答案 0 :(得分:5)

对于上述问题(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类型别名可以通过检查weak types的哪些单个属性扩展为answer to a related question来工作,如{{3}中所述}。索引签名使这一点搞砸了,因此,如果您需要对具有索引签名的类型进行操作,则需要更复杂的东西(在链接的答案中也有介绍)。

一旦您弄清了哪些键是可选的,使用PickOmit以及T和{{1} }和交集:Partial。唯一的问题是这种类型相对难看。如果将类型写为单个对象类型会更好。

在这里,我对以下格式的conditional type inference使用技巧。假设我们有一个Required类型,其中包含许多交集和映射。为了使它漂亮,我们可以做

Required<Pick<T, OptionalKeys<T>>> & Partial<Omit<T, OptionalKeys<T>>>

在这里,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等效。


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

Link to code

答案 1 :(得分:3)

您可以使用像这样的类型系统来构造Flip<T>

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;