我有一些类似于这些的接口,以后要排除的接口仅包含可选属性:
interface P {
id: string
}
interface A extends P {
attrA: string
}
interface B extends P {
attrB?: string
}
现在从与其他接口的联合中排除此接口不起作用:
type R = Exclude<A | B, B> // expected: A, is: never
类型R
为never
,但应为A
。
如果Exclude<T, U> = T extends U ? never : T
仅由可选属性组成,我可以看到A extends B
显然是true
是B
。为什么会这样?
又如何从接口的联合类型中适当排除类型?
答案 0 :(得分:2)
给出以下类型:
interface P {
id: string
}
interface A extends P {
attrA: string
}
interface B extends P {
attrB?: string
}
type R = Exclude<A | B, B>; // never
R
的类型为never
,因为编译器认为A extends B
是真实的:
const a: A = { id: "a", attrA: "A" };
const b: B = a; // okay
您可以看到a
是有效的A
,但它也是有效的B
。 TypeScript中的对象类型不是exact;您可以通过添加属性来扩展类型(这就是为什么A
可以分配给P
的原因,即使A
具有额外的属性)。从类型系统的角度来看,每个类型A
的值也是一个类型B
的值,因此Exclude<A | B, B>
会同时删除A
和B
来自工会,您将剩下never
。
当然,将A
分配给B
实际上并不安全。编译器假定类型A
的每个值也是B
类型,但实际上,它更像几乎的每个值。有些A
类型的特定值不能分配给B
类型,即具有attrB
属性且类型不兼容的任何值:
const aButNotB = {id: "a", attrA: "A", attrB: 123}
const alsoA: A = aButNotB; // okay
const notB: B = aButNotB; // error, attrB is incompatible
const butWait: B = alsoA; // no error?! attrB is still incompatible, but compiler forgot!
但是,由于TypeScript尚不支持negated types,因此TypeScript中无法将“ A
之外的每个B
”表示为具体类型。因此,当比较两种对象类型时,编译器只是忽略它们中仅一种存在的可选属性...导致了这一盲点。
这就是为什么它发生的原因。至于解决方法,则取决于您的用例。
理想情况下,如果确实需要区分类型联合的值,则可以使用discriminated union。这意味着联合应该包含一些或多个可以绝对区分它们的属性。例如,假设添加一个type
判别属性:
interface Aʹ extends A {
type: "A";
}
interface Bʹ extends B {
type: "B";
}
type Rʹ = Exclude<Aʹ | Bʹ, Bʹ>; // Aʹ
现在无法将类型Aʹ
的值分配给类型Bʹ
,反之亦然,现在Exclude
的行为符合您的预期。
或者,如果您只是按照预期的方式来研究Exclude
,可以在处理它们之前先修改A
和B
类型,然后再取消修改结果,像这样:
type OptToUndef<T> = {
[K in keyof Required<T>]: T[K] | ({} extends Pick<T, K> ? undefined : never)
};
type UndefToOpt<T> = (Partial<
Pick<T, { [K in keyof T]: undefined extends T[K] ? K : never }[keyof T]>
> &
Pick<
T,
{ [K in keyof T]: undefined extends T[K] ? never : K }[keyof T]
>) extends infer O
? { [K in keyof O]: O[K] }
: never;
基本上,OptToUndef<T>
的对象类型为T
,并要求所有可选类型,但它们的属性类型包括undefined
。 UndefToOpt<T>
的对象类型为T
,并将所有类型为undefined
的属性都设为可选属性。这些差不多是彼此的逆运算(只要您没有必需的类型,包括undefined
)。然后,您可以执行以下操作:
type UA = OptToUndef<A>; // {id: string; attrA: string }
type UB = OptToUndef<B>; // {id: string; attrB: string | undefined }
type UR = Exclude<UA | UB, UB>; // same as UA
type Rfixed = UndefToOpt<UR>; // same as A
类似的事情可能对您有用,您可以将B
调整为非全选,然后在完成后取消调整。
好的,希望能有所帮助;祝你好运!