理解 TypeScript 泛型

释放双眼,带上耳机,听听看~!

理解 TypeScript 泛型

TypeScript 是一种强大的编程语言。它帮助我捕获愚蠢的错误,更快地设计和迭代软件,并简单地编写更好的代码。

话虽如此,我不能说我完全理解 TypeScript。这并不是一件坏事。使用一门语言的乐趣之一是探索并了解它的行为、怪癖和隐藏的宝石。

今天,我想分享一个特殊的时刻,当时我意识到泛型并不是我想象的那样。

泛 型

在 TypeScript 中,你可以通过为其分配类型来注释几乎任何内容。例如,让我们创建一个函数,该函数接受 a 并将该消息注释为字符串:print()message

function print(message: string) {}

在这里,我们告诉 TypeScript 参数必须是 .目前为止,一切都好。messagestring

如果我们希望我们的函数也支持打印数字,我们可以将 the 的类型转换为联合,它表示类型的组合。print()message

function print(message: string | number) {}

现在 是 和 的联合,这意味着它可以是 either。messagestringnumber

除了打印给定的消息外,我们还想从函数中返回它。所以如果我们传递一个字符串,它返回一个字符串类型,如果我们传递一个数字,则返回类型应该是 number。print()print()

但是,如果我们尝试使用相同的联合类型方法,我们将不会走得太远。

function print(message: string | number): string | number {}

该函数现在确实可以返回字符串或数字,但返回类型与我们传入的类型没有关系。我们可以传递一个字符串,而 的返回类型仍然是它真正应该只是 。messageprint()string | numberstring

要派生 的返回类型 ,我们需要使用一个泛型,它看起来有点像这样:print()

function print<MessageType>(message: MessageType): MessageType {}

这就是事情变得有点纠结的地方。

与使用简单表示法将类型分配给参数不同,使用泛型并不会真正影响 .至少不是你想象的那样。message: string | numbermessage

推理

最让我印象深刻的是,我意识到在使用泛型时,类型 “flow” 是相反的。

 ↓──────────(1)────────┐
function print(message: MessageType): MessageType {}
 └─────────────────(2)───────────────↑

(1) 文本类型 of 被指定为泛型类型; (2) 推断的泛型类型用于注释 的返回类型 。messageMessageTypeMessageTypeprint()

泛型不是说“现在 is of type ”,而是将作为参数传递的任何文本类型存储在名为 .messageMessageTypeMessageTypemessageMessageType

泛型类似于类型的变量。

因此,如果我们传递字符串 message,则泛型的值将为:MessageTypestring

print('hello')
print<'hello'>(message: 'hello'): 'hello

泛型的这种 “存储” 文本类型的能力称为推理。如果你留心的话,你已经注意到推断出的不是纯字符串类型,而是文字 .这是泛型的另一个巧妙特性 — 它们将推断出可能的最窄类型( 是较窄的字符串类型)。MessageType'hello''hello'

但是 TypeScript 是怎么知道的呢?

TypeScript 使用编译器 () 根据你的代码计算类型。就像你在调用函数之前不知道参数的实际值一样,TypeScript 不知道泛型 untilll 的类型,嗯,你调用函数!tscmessageprint()MessageTypeprint()

好吧,你可能想知道:泛型不描述而是推断类型。那么,如何将 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 传递,则不返回任何内容。要在类型级别描述此行为,我们需要检查泛型的实际类型,并使函数的返回类型为有条件的:numberMessageTypeprint()

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 类型)。MessageTypemessage

结论

我希望你今天了解到 TypeScript 中的泛型基本上是类型的变量。您可以从代码中的实际类型推断出它们的值,使用约束类型缩小它们的范围,甚至根据这些值编写条件类型逻辑。

泛型真棒!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
技术教程

函数思考,第一部分:输入/输出模式

2024-9-2 14:28:30

技术教程

Null 和 Undefined 之间的区别

2024-9-2 15:37:11

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索