对于您所说的问题(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 中所述。索引签名搞砸了,所以如果您需要对具有索引签名的类型进行操作,您将需要更复杂的东西(在链接的答案中也有描述)。
一旦您确定了哪些键是可选的,就可以相对简单地构造一个与您想要的等效类型,Pick 和 Omit 以及 Partial 和 Required 以及一个交集:Required<Pick<T, OptionalKeys<T>>> & Partial<Omit<T, OptionalKeys<T>>> .唯一的问题是这种类型比较难看;如果将类型写为单个对象类型会更好。
这里我使用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<Bar> 给出了一个等价于Foo 的类型。
好的,希望对您有所帮助。祝你好运!
Link to code