与 Typescript 中的“extends”关键字混淆

VIV*_*VID 2 javascript types typescript

想想 Typescript 中的以下代码行:

\n
let x: 'a' | 'b' extends 'a' ? true : false;\n
Run Code Online (Sandbox Code Playgroud)\n

我想知道 的类型x是否正确,因为直观上'a' | 'b'是 的扩展版本'a'(至少我的直觉是这样说的)。我认为extends它会像数学中的子集一样工作。A extends B当且仅当B \xe2\x8a\x86 A

\n

不过,实际类型似乎就xfalse这里。我想我不明白这个extends关键字到底是如何工作的。

\n

Jef*_*ica 7

根据里氏替换原则,如果“Y 扩展 X”或等效地“Y 是 X 的子类型”,则只要请求符合类型 X 的值,就可以使用符合类型 Y 的值。子类型类型更具体,而不是更宽松。这导致了一个反直觉的结论:如果 Y 延伸 X,则 Y 比 X 受到更多约束。所有 Y 都是 X,但并非所有 X 都是 Y。

\n

在您的示例中,因为\'a\' | \'b\'可能是\'a\'\'b\',所以该联合类型不会扩展 type \'a\'\'b\'不会替代\'a\'。相反,\'a\' extends (\'a\' | \'b\')因为所有匹配的值都\'a\'可以在\'a\' | \'b\'请求的地方工作。

\n

因此,当且A extends B仅当A \xe2\x8a\x86 B

\n

这在 TypeScript 示例中不太直观的原因之一是它处理文字值的联合。对于我们来说,从对象的角度来思考这个问题可能更有意义,其中{foo: number, bar: number} extends {foo: number}。后一个超类型{foo: number}可以具有bar任何类型的属性,也可以bar根本没有属性。前一种子类型{foo: number, bar: number}更具体,也更受限制:不仅是foo数字,而且bar也是数字。这也与类或接口定义中的使用相匹配extends:子类或子接口添加属性和方法,与超类或超接口相比,这进一步限制了实例。

\n

never这个解释与可分配给所有内容的事实一致:never是最受约束的类型,因为没有实际值与其匹配,因此never扩展了所有内容。空集是每个集合的子集;空类型never是每个类型的子类型,可以分配给每个其他类型

\n
let unionExtendsMember: \'a\' | \'b\' extends \'a\' ? true : false;\n//  ^? let unionExtendsMember: false\n\nlet memberExtendsUnion: \'a\' extends \'a\' | \'b\' ? true : false;\n//  ^? let memberExtendsUnion: true\n\nlet objectExtendsObject: {foo: number, bar: number} extends {foo: number} ? true : false;\n//  ^? let objectExtendsObject: true\n\nlet neverExtendsObject: never extends { foo: number } ? true : false;\n//  ^? let neverExtendsObject: true\n
Run Code Online (Sandbox Code Playgroud)\n

游乐场链接

\n