鉴于这些类型:
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,剩下的是@987654341 @。
当然,将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 无法将“Every A which is not 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 的属性变为可选属性。这些或多或少是彼此的逆运算(只要您没有 required 类型,包括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 调整为不是全部可选的,然后在完成后取消调整。
好的,希望对您有所帮助;祝你好运!
Link to code