发布于 2026-01-06 0 阅读
0

TypeScript 入门第三部分 - 泛型及其他 泛​​型 复杂类型 声明文件 声明合并 就这些吗? 资源 DEV 的全球展示挑战赛 由 Mux 呈现:展示你的项目!

TypeScript 入门第三部分 - 泛型及其他

仿制药

复杂类型

申报文件

声明合并

就是这样?

资源

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

这篇文章摘自我的博客,记得去看看博客上的最新内容哦😉

在这里,我们将继续TypeScript 入门教程。如果您还没看过第一部分第二部分,请务必先阅读它们,以便快速上手 TypeScript,并了解本教程的内容。😉 在本文中,我们将最终探索泛型、一些复杂类型声明文件。读完本系列教程后,您应该已经掌握了足够的 TypeScript 知识,可以编写非常复杂的代码了。那么,尽情享受吧! 🙂

仿制药

让我们从重要的内容开始!泛型(因为我们接下来要讨论的就是它)在 TypeScript 和其他一些支持泛型的静态类型语言中非常重要。但是,泛型究竟是什么呢?

可以合理推断,“generics”(通用)一词源于“general”(通用的),在此语境中意为“相同”。请看下面的函数。

function myFunction(arg: any): any {
    return arg;
}

Enter fullscreen mode Exit fullscreen mode

我们的函数接受任意类型的参数并直接返回它(我知道,这其实没什么用😅)。我们都知道,“任意类型”并不是类型安全的。它也没有表明返回类型与参数类型相同(虽然代码中可以读出这一点,但编译器却无法识别)。我们希望表明这些类型完全相同。不使用联合体、别名或其他任何特殊机制——绝对相同!这就是泛型发挥作用的地方。

function myGenericFunction<T>(arg: T): T {
    return arg;
}

Enter fullscreen mode Exit fullscreen mode

好了,这就是我们的通用函数……还有一些新的语法。😄 在<>类型参数声明部分之前,我们使用尖括号 ( ) 声明一个T类型(T是泛型类型最常用的名称,通常单字母比长名称更合适)。然后我们表明参数和返回值类型相同,但使用这个T类型。这才是真正的通用 😁,因为同一个变量类型在多个地方被使用。

但是它的T类型是什么呢?是 `int` stringnumber`int` 还是 `int` 等等?嗯,它可以是其中任何一种。调用泛型函数有两种方法。

myGenericFunction<string>('str');

Enter fullscreen mode Exit fullscreen mode

第一种方法要求你直接指定实际类型,而不是使用 ` Ttype`。这里我们使用 `T` string。我们用类似的尖括号语法来表示这一点(这种语法在泛型中非常常见)。这样,必需参数的类型string以及返回值的类型都会变为 `T`。显然,这比使用`T` 或联合类型更好,也更安全。any

myGenericFunction(10);

Enter fullscreen mode Exit fullscreen mode

第二种方法更常用,它利用了 TypeScript 类型推断和更具体的参数类型推断。这正是泛型的优势所在。我们T10参数推断出的类型与参数的类型相同。这种选择之后会在所有使用类型number的地方得到体现。T

到目前为止,你应该对泛型有了相当不错的理解。但是,通过上面的例子,我知道你可能对它们的实用性有所怀疑。请相信我——你迟早会用到泛型(如果你用 TypeScript 编程的话😂),到那时你就会发现它们的潜力。尤其是在结合一些复杂类型(我们稍后会详细学习)或类型守卫时,泛型的作用更加显著。类型守卫能让你更充分地利用泛型。

另外,请记住函数中泛型类型的位置。它应该始终位于圆括号( ()) 之前,也就是参数部分之前。箭头函数也一样。更通用的做法是,将它们放置在之后调用时可以安全地添加尖括号的位置。你很可能会习惯这种做法。

通用世界

是的,泛型函数确实存在,但你知道泛型在整个 TypeScript 类型系统中都广泛应用吗?几乎在所有适用的地方都可以使用它们,尤其是在类接口中

class MyGenericClass<T, U> {
    myProperty: T;
    myProperty2: U;
    constructor(arg: T) {
        this.myProperty = arg;
    }
}

Enter fullscreen mode Exit fullscreen mode

如你所见,类与泛型配合使用效果非常好。就像在函数中一样,泛型类型可以在声明的上下文中的任何位置使用。我有没有提到你可以声明多个泛型类型?这适用于所有可以使用泛型的地方。只需用逗号 ( ,) 分隔泛型类型名称即可。

interface MyGenericInterface<T> {
    myProperty: T;
    myProperty2: T[];
}

Enter fullscreen mode Exit fullscreen mode

上面是使用泛型连接接口的示例,看起来和使用类时一样。请注意,第二个属性是一个 T 类型的数组。我只是想再次展示 TypeScript 类型系统的所有组件是如何协同工作的

由于类和接口与函数不同,因此不能使用参数类型推断来调用它们。你只能使用第一种方法——直接传递特定类型。否则,T 将等于一个空对象字面量

interface MyGenericInterface<T> {
    myProperty: T
}

class MyGenericClass <U> {
    myProperty: MyGenericInterface<U>;

    constructor(arg: U) {
        this.myProperty = {
            myProperty: arg
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

这个例子也展示了如何嵌套并更好地利用泛型。注意我们是如何将类泛型类型传递UMyGenericInterface`in` 的myProperty

另一个数组

最后,泛型部分还有一点需要补充。还记得我们之前使用特殊语法来指定数组类型吗?string[]其实还有另一种方法可以实现同样的效果。你可以使用内置的泛型数组接口,轻松实现相同的结果Array<string>。这是一种非常常见的做法。你可以在官方的TypeScript 标准库(所有 JavaScript 特性、Web API 等的类型定义/声明文件)以及其他一些流行的声明文件(我们稍后会介绍)中看到它,例如 React 的声明文件。

复杂类型

有了泛型,你就能体验到全新的可能性。现在我们可以探索一些类型,它们与泛型结合使用,能让你拥有更精细的控制。有了它们,你可以表达出非常有趣的结构。不过,现在也是时候去探索它们了!😎

扩展类型

你已经知道extends可以与类和接口一起使用的关键字。但在 TypeScript 中,它也有与泛型一起使用的用例。在这里,你可以使用它来限制/指定泛型类型应该继承自的类型。让我用一个例子来解释一下。

function myGenericFunction<T extends string>(arg: T): T {
    return arg;
}

Enter fullscreen mode Exit fullscreen mode

这里我们直接指定泛型类型应该继承自字符串类型。自然地,这很可能意味着它应该只是一个字符串string。但是,当你将类型指定为某种类时,它的派生类型也可以赋值。通常,这允许你更好地指定泛型类型及其应具有的属性,就像extends类和接口一样。

条件类型

条件类型是 TypeScript 类型系统中相当新的概念。它在 TypeScript v2.8中引入,允许你根据条件检查选择正确的类型。检查可以使用我们熟知的extends关键字和简单的语法来完成:

type MyType<T> = T extends string ? boolean : number;

Enter fullscreen mode Exit fullscreen mode

上面我们定义了一个类型别名(也可以是泛型),并为其分配了一个条件类型。我们检查泛型类型 T 是否继承自字符串类型。如果是,则解析为布尔值;否则,解析为数字。当然,你也可以将此技巧应用于其他类型,以及嵌套多个 if 语句(反正它们都是类型 😉)。

索引类型

索引签名

我们之前已经讲解过如何在类、接口或对象字面量中声明属性。但是,如果想要创建一个包含任意数量键的对象,且每个键的类型都相同,该怎么办呢?当然,TypeScript 也提供了解决方案!😯

interface MyInterface {
    [key: string]: number;
}

Enter fullscreen mode Exit fullscreen mode

此功能称为索引签名,可用于接口、类和对象字面量。其语法由方括号 ( []) 组成,方括号内包含属性键的通用名称及其类型(通常为字符串,可选数字)。之后是属性值的类型。您可以将其理解为:每个属性(在本例中为字符串类型的键)都应该有一个数字类型的值。

请记住,TS 类型可以混合使用,因此您可以自由地使用索引签名,并结合可选指示符或默认值等技巧。此外,在创建除了索引签名之外还具有其他属性的结构时,请记住,这些属性也必须能够分配给已声明的签名!

关键

假设你有一个对象、接口或其他任何东西,并且想要创建一个函数,该函数接受对象的属性名作为参数并返回其值。当然,你可以直接将参数类型声明为字符串,但这样做不如使用字符串字面量联合体那样能获得 IDE 的充分支持。而这正是keyof运算符的用武之地。

const myObject = {
    a: 1,
    b: 2,
    c: 3
}

function getProperty<T extends keyof (typeof myObject)>(propertyName: T): (typeof myObject)[T] {
    return myObject[propertyName];
}

Enter fullscreen mode Exit fullscreen mode

这里涉及到一些复杂的类型定义!花点时间自己分析一下。它基本上允许我们将参数明确地定义为联合类型,'a'|'b'|'c'并添加了真正具体的返回类型声明。

索引访问

在前面的例子中,你应该看到了返回类型使用了类似于 JavaScript方括号表示法的符号来访问对象属性。而我们在这里所做的也几乎完全相同,只不过这次是针对类型!

interface MyInterface {
    myStringProperty: string
}

type MyString = MyInterface['myStringProperty'];

Enter fullscreen mode Exit fullscreen mode

这里我们访问了myStringProperty`of`MyInterface并将其赋值给MyString类型别名,结果等于字符串。明白了吧?🚀

映射类型

映射类型顾名思义,允许将你的类型映射/转换为不同的形式。有了它们,你可以处理给定的类型,并以任何你想要的方式对其进行更改。

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
}

Enter fullscreen mode Exit fullscreen mode

这里有一个实际的例子。我们的泛型Readonly类型接受T一个类型并对其进行转换,使每个属性都变为只读。语法类似于索引签名,但略有不同。我们不再使用标准的属性名及其类型对,而是使用一个in关键字。这允许我们遍历类型键的并集(类似于for...inT循环) ,从而定义P类型(字符串字面量)。一般来说,我们遍历 T 类型的属性并更改它们以创建一个新类型。就像.map()JavaScript 数组的方法一样。😉

申报文件

TypeScript 作为 JavaScript 的超集,可以轻松受益于 JS强大的生态系统和丰富的库。但类型推断并非万能。在这种情况下,它会使用任意类型,导致类型安全性降低。为了解决这个问题,TS 提供了创建类型声明文件(也称为typings)的选项。这些文件通常以.d.ts为扩展名,它们向 TS 编译器提供有关 JS 代码中类型的信息。这使得在 TS 中使用 JS 库能够保证高质量的类型安全性

许多流行的 JS 库已经提供了自己的类型定义,它们要么打包在NPM包中,要么作为DefinitelyTyped仓库的一部分单独提供。但是,如果你选择的库没有类型定义文件,你可以根据该工具的文档和其他资源快速创建自己的类型定义。

创建自己的类型定义并不比编写 TypeScript 代码难多少,只是不涉及 JavaScript 部分,也就是说只涉及类型。此外,你通常需要declare在函数和变量前使用 `@type` 关键字来声明它们。官方 TypeScript 文档对此主题有很好的阐述,如果你感兴趣,可以去看看。

声明合并

声明合并是 TypeScript 中的一个重要概念,它允许你将给定结构的多个声明合并为一个。以下是一个合并两个相同接口声明的示例。

interface MyInterface {
    myStringProperty: string;
}
interface MyInterface {
    myNumberProperty: number;
}

Enter fullscreen mode Exit fullscreen mode

最终生成的接口名称将MyInterface包含两个分别声明的属性。同样的方法也适用于其他一些 TypeScript 结构,例如(部分适用)、枚举命名空间

模块扩充

当您需要在多个 JS 模块中扩展/更改给定值时,为了提供足够的类型安全性,您需要使用模块扩展。您可以使用declare module关键字对来实现这一点。

import MyClass from './classes';

declare module './classes` {
    interface MyClass {
        myBooleanProperty: boolean;
    }
}
MyClass.prototype.myBooleanProperty = true;

Enter fullscreen mode Exit fullscreen mode

就是这样?

本文几乎涵盖了编写专业 TypeScript 代码所需的一切。虽然还有一些其他特性,例如命名空间mixin,但就我近两年的编码经验而言,我并不觉得它们有多必要,甚至觉得它们没什么用。

综上所述,我认为 TypeScript 入门教程到此结束。当然,如果您有兴趣,请务必阅读前两部分。或许您想在这个博客上看到更多关于 TypeScript 的内容?比如完整的TypeScript 配置文件概述,或者关于如何运用本系列所学知识的教程?请在评论区留言或分享您的想法。👏

和往常一样,欢迎在TwitterFacebook上关注我,获取更多内容。也欢迎访问我的个人博客。🚀

资源

文章来源:https://dev.to/areknawo/typescript-introduction-part-iii---generics--stuff-2ef3