假设我具有以下类型:
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;
}
Partial
和Required
的实现非常简单,但是这种Flip
类型需要知道Foo
的哪些属性是可选的,哪些是必需的。是否可以使用泛型阅读此修改内容?
某些情况下:我认为这对于React中的defaultProps
是有用的类型。
答案 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}中所述}。索引签名使这一点搞砸了,因此,如果您需要对具有索引签名的类型进行操作,则需要更复杂的东西(在链接的答案中也有介绍)。
一旦您弄清了哪些键是可选的,使用Pick
和Omit
以及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
等效。
好的,希望能有所帮助。祝你好运!
答案 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;