TypeScript 是一种强大的编程语言。它帮助我捕获愚蠢的错误,更快地设计和迭代软件,并简单地编写更好的代码。
话虽如此,我不能说我完全理解 TypeScript。这并不是一件坏事。使用一门语言的乐趣之一是探索并了解它的行为、怪癖和隐藏的宝石。
今天,我想分享一个特殊的时刻,当时我意识到泛型并不是我想象的那样。
泛 型
在 TypeScript 中,你可以通过为其分配类型来注释几乎任何内容。例如,让我们创建一个函数,该函数接受 a 并将该消息注释为字符串:print()
message
function print(message: string) {}
在这里,我们告诉 TypeScript 参数必须是 .目前为止,一切都好。message
string
如果我们希望我们的函数也支持打印数字,我们可以将 the 的类型转换为联合,它表示类型的组合。print()
message
function print(message: string | number) {}
现在 是 和 的联合,这意味着它可以是 either。message
string
number
除了打印给定的消息外,我们还想从函数中返回它。所以如果我们传递一个字符串,它返回一个字符串类型,如果我们传递一个数字,则返回类型应该是 number。print()
print()
但是,如果我们尝试使用相同的联合类型方法,我们将不会走得太远。
function print(message: string | number): string | number {}
该函数现在确实可以返回字符串或数字,但返回类型与我们传入的类型没有关系。我们可以传递一个字符串,而 的返回类型仍然是它真正应该只是 。message
print()
string | number
string
要派生 的返回类型 ,我们需要使用一个泛型,它看起来有点像这样:print()
function print<MessageType>(message: MessageType): MessageType {}
这就是事情变得有点纠结的地方。
与使用简单表示法将类型分配给参数不同,使用泛型并不会真正影响 .至少不是你想象的那样。message: string | number
message
推理
最让我印象深刻的是,我意识到在使用泛型时,类型 “flow” 是相反的。
↓──────────(1)────────┐ function print(message: MessageType): MessageType {} └─────────────────(2)───────────────↑
(1) 文本类型 of 被指定为泛型类型; (2) 推断的泛型类型用于注释 的返回类型 。
message
MessageType
MessageType
print()
泛型不是说“现在 is of type ”,而是将作为参数传递的任何文本类型存储在名为 .message
MessageType
MessageType
message
MessageType
因此,如果我们传递字符串 message,则泛型的值将为:MessageType
string
print('hello') print<'hello'>(message: 'hello'): 'hello
泛型的这种 “存储” 文本类型的能力称为推理。如果你留心的话,你已经注意到推断出的不是纯字符串类型,而是文字 .这是泛型的另一个巧妙特性 — 它们将推断出可能的最窄类型( 是较窄的字符串类型)。MessageType
'hello'
'hello'
但是 TypeScript 是怎么知道的呢?
TypeScript 使用编译器 () 根据你的代码计算类型。就像你在调用函数之前不知道参数的实际值一样,TypeScript 不知道泛型 untilll 的类型,嗯,你调用函数!
tsc
message
print()
MessageType
print()
好吧,你可能想知道:泛型不描述而是推断类型。那么,如何将 to 注释为字符串或数字呢?message
通用约束
与描述参数的允许类型非常相似,您可以描述泛型的允许类型。这些描述称为 “constraints”,它们在泛型的定义旁边使用一个特殊的关键字:extends
// The "MessageType" generic can be either a string or a number. // If it's anything else, that's a type error. function print( message: MessageType ): MessageType {}
如果我必须“解包”这段代码,我会这样描述它(当心,前面有伪代码!)
// Declare a generic called "MessageType" that is // of type string or number. generic MessageType = string | number // Assign the actual type of "message" as the value // of the "MessageType" generic. function print(message: InferAs): MessageType {}
这应该有助于你将泛型可视化为类型的变量,稍后当你为函数、类、对象、接口等提供实际值时,这些变量会被分配。
条件类型
由于我们可以使用泛型存储类型,因此我们可以根据这些泛型的值创建不同的类型行为。
例如,我们只返回字符串 messages,如果 the 作为 message 传递,则不返回任何内容。要在类型级别描述此行为,我们需要检查泛型的实际类型,并使函数的返回类型为有条件的:number
MessageType
print()
function print(message: MessageTypeMessageType extends string ? MessageType : void {}
这是一个条件类型:
//If "MessageType" is a string, then return it as-is. //Else, return "void". MessageType extends string ? MessageType : void
这是一个类型表达式,你不能只是阅读并说“好吧,这是类型 X”。因为此表达式的值将根据值而有所不同(而值又取决于参数的 Literal 类型)。MessageType
message
结论
我希望你今天了解到 TypeScript 中的泛型基本上是类型的变量。您可以从代码中的实际类型推断出它们的值,使用约束类型缩小它们的范围,甚至根据这些值编写条件类型逻辑。
泛型真棒!