为什么 Partial 接受来自其他类型的额外属性?

Why Partial accepts extra properties from another type?

给定接口 A 和 B,它们包含一个 x1 属性 共同点

interface A {
  a1: number;
  a2: number;
  x1: number;  // <<<<
}

interface B{
  b1: number;
  x1: number;  // <<<<
}

并给出实现 ab

let a: A = {a1: 1, a2: 1, x1: 1};
let b: B = {b1: 1, x1: 1};

这只是通过,即使 b1 不属于 Partial<A>:

let partialA: Partial<A> = b;

但这失败了,因为 b1 不属于 Partial<A>:

let partialA: Partial<A> = {b1: 1, x1: 1};

谁能告诉我为什么?

这将是一段漫长的旅程,所以请和我一起坚持下去:

通常 sub-type 应该可以分配给 base-type。在 Typescript 中,应该可以将具有更多属性的类型分配给只具有属性子集的类型。因此,例如,这是合法的:

let source = { a1: 0, a2: 0}
let target: { a1: number } = source

现在令人惊讶的是,由于结构类型的工作方式,Partial<A>Partial<B> 的子类型,而 Partial<B>Partial<A> 的子类型。子类型中可以缺少可选属性,因此类型中缺少的可选属性不会取消类型作为子类型的资格。如果我们删除可选属性,我们只剩下 {},它可以是任何其他类型的基类型。如果我们要求编译器使用条件类型回答这个子类型化问题,那么编译器同意我的观点:

type q1 = Partial<A> extends Partial<B> ? "Y" : "N" // Y
type q2  = Partial<B> extends Partial<A> ? "Y" : "N" // Y

对此有一个例外(好吧也许有两个),将对象文字直接分配给特定类型的引用。这称为过度 属性 检查,因为如果我们直接执行上述赋值,我们会得到一个错误:

let target: { a1: number } = { a1: 0, a2: 0} // err  

这是一个错误的原因是,错误地创建一个具有更多属性或拼写错误的属性的对象文字是一个常见的错误,并且这种检查(这违反了 sub-type 应该是可分配的原则到 base-type) 是为了捕捉这个错误。这也是您在

上收到错误的原因
let partialA: Partial<A> = {b1: 1, x1: 1};

但过多的 属性 检查只会在 直接 将对象文字赋值给特定类型的变量时起作用。因此,在赋值 let partialA: Partial<A> = b; 时,它不会在超出 属性 检查时触发错误。

更复杂的是 Partial<A> 是所谓的弱类型。从 PR 介绍检查这种类型:

A weak type is one which has only optional properties and is not empty. Because these types are assignable from anything except types with matching non-assignable properties, they are very weakly typechecked. The simple fix here is to require that a type is only assignable to a weak type if they are not completely disjoint.

现在,由于弱类型没有强制属性,遵循 sub-type 应可分配给 base-type 的原则,任何其他对象都可分配给此类类型:

let k = { foo: 0 }
let partialA: Partial<A> = k;

k 的类型是 Partial<A> 的 sub-type,当然 kPartial<A> 没有任何共同点,但这不是问题。毕竟 Partial<A> 不需要任何属性,所以 k 不需要任何属性,而且它多了一个 属性,这通常是 sub-type 所做的,变得更具体通过添加成员。

上面的代码代码是错误的原因是因为上面的 PR 是参考,它假定将完全不相关的类型分配给弱类型可能是错误的。因此,就像 excess 属性 检查一样,引入了一个规则(该规则再次打破了子类型可分配给基类型的想法),该规则不允许此类分配。

然而在你的情况下 Partial<A>Partial<B> 确实有一些共同点 x1 所以这个规则不会捕捉到可能错误的分配。虽然令人惊讶,因为 Partial<A>Partial<B> 都没有强制属性,但它们是彼此的子类型,除非有特定原因(例如额外的 属性 检查或这个额外的弱类型检测规则) 允许赋值。