首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 重塑“类型思维”
02 | 类型基础(1):强类型与弱类型
03 | 类型基础(2):动态类型与静态类型
04 | 编写你的第一个TypeScript程序
05 | 基本类型
06 | 枚举类型
07 | 接口(1):对象类型接口
08 | 接口(2):函数类型接口
09 | 函数相关知识点梳理
10 | 类(1):继承和成员修饰符
11 | 类(2):抽象类与多态
12 | 类与接口的关系
13 | 泛型(1):泛型函数与泛型接口
14 | 泛型(2):泛型类与泛型约束
15 | 类型检查机制(1):类型推断
16 | 类型检查机制(2):类型兼容性
17 | 类型检查机制(3):类型保护
18 | 高级类型(1):交叉类型与联合类型
19 | 高级类型(2):索引类型
20 | 高级类型(3):映射类型
21 | 高级类型(4):条件类型
22 | ES6与CommonJS的模块系统
23 | 使用命名空间
24 | 理解声明合并
25 | 如何编写声明文件
26 | 配置tsconfig.json(1):文件选项
27 | 配置tsconfig.json(2):编译选项
28 | 配置tsconfig.json(3):工程引用
29 | 编译工具:从ts-loader到Babel
30 | 代码检查工具:从TSLint到ESLint
31 | 使用Jest进行单元测试
32 | 创建项目
33 | 组件与类型(1):函数组件与类组件
34 | 组件与类型(2):高阶组件与Hooks
35 | 事件处理与数据请求
36 | 列表渲染与路由
37 | Redux与类型
38 | 搭建服务端开发环境
39 | 列表的CRUD
40 | 导出Excel
41 | 搭建Vue开发环境
42 | 组件封装
43 | 组件发布
44 | 共存策略
45 | 宽松策略
46 | 严格策略
当前位置:
首页>>
技术小册>>
TypeScript开发实战
小册名称:TypeScript开发实战
### 第十四章 泛型(2):泛型类与泛型约束 在TypeScript的世界中,泛型不仅是一种类型安全的代码复用工具,它还是构建灵活、可扩展的库和框架的基石。在前面的章节中,我们已经探索了泛型函数的基本用法,它们如何帮助我们编写与类型无关的代码。本章将深入泛型的另一个重要应用——泛型类与泛型约束,这将进一步增强我们利用TypeScript进行类型检查的能力,使得代码更加健壮和易于维护。 #### 14.1 泛型类的基础 泛型类允许我们在类定义时声明一个或多个类型参数,这些类型参数将在类的实例化时被指定。这样,类的方法就可以利用这些类型参数来提供类型安全的功能。泛型类极大地提高了代码的复用性和灵活性。 **示例:创建一个简单的泛型类** ```typescript class Box<T> { private value: T; constructor(value: T) { this.value = value; } getValue(): T { return this.value; } setValue(newValue: T): void { this.value = newValue; } } // 使用泛型类 let stringBox = new Box<string>("Hello, TypeScript!"); console.log(stringBox.getValue()); // 输出: Hello, TypeScript! let numberBox = new Box<number>(123); console.log(numberBox.getValue()); // 输出: 123 ``` 在上述示例中,`Box`类是一个泛型类,它有一个类型参数`T`。这个类型参数被用于`value`属性的类型定义、`getValue`方法的返回类型以及`setValue`方法的参数类型。这样,我们就可以创建`Box`的实例来存储任何类型的值,同时保持类型安全。 #### 14.2 泛型约束 虽然泛型类提供了强大的类型灵活性,但在某些情况下,我们可能希望对类型参数施加一些限制,以确保类的方法可以安全地执行某些操作。这就是泛型约束的作用所在。通过泛型约束,我们可以指定一个接口,要求所有传入的类型参数都必须实现这个接口。 **示例:使用泛型约束限制类型参数** 假设我们想要扩展`Box`类,使其能够处理任何具有`length`属性的值(如字符串、数组等)。我们可以通过定义一个接口并将其作为泛型约束来实现这一点。 ```typescript interface Lengthwise { length: number; } class BoxWithLength<T extends Lengthwise> { private value: T; constructor(value: T) { this.value = value; } getLength(): number { return this.value.length; } // 其他方法... } // 正确使用 let stringBox = new BoxWithLength<string>("Hello, TypeScript!"); console.log(stringBox.getLength()); // 输出: 15 let arrayBox = new BoxWithLength<number[]>([1, 2, 3]); console.log(arrayBox.getLength()); // 输出: 3 // 错误使用:类型{}没有length属性 // let objectBox = new BoxWithLength<{}>({}); // TypeScript编译器会报错 ``` 在上面的例子中,我们定义了一个`Lengthwise`接口,它要求任何实现它的类型都必须有一个`length`属性。然后,我们将`BoxWithLength`类声明为泛型类,并使用`extends`关键字来指定类型参数`T`必须扩展自`Lengthwise`接口。这样,任何尝试创建`BoxWithLength`实例时传入不满足`Lengthwise`接口的类型都将导致TypeScript编译器报错。 #### 14.3 泛型约束的进阶应用 泛型约束不仅可以用于简单的属性检查,还可以与索引签名、构造函数签名、方法签名等结合使用,以提供更复杂的类型检查和约束。 **示例:使用索引签名进行泛型约束** 假设我们想要一个能够处理任何对象(这些对象的属性值都是同一类型)的泛型类。我们可以通过在约束接口中使用索引签名来实现这一点。 ```typescript interface KeyedCollection<T> { [key: string]: T; } class Collection<T extends KeyedCollection<any>> { private items: T; constructor(items: T) { this.items = items; } getItem(key: string): any { return this.items[key]; } // 注意:这里我们假设getItem总是返回any,因为T的值类型可以是任意类型 // 在实际应用中,你可能需要更精细的类型控制 } let myCollection = new Collection<{ name: string; age: number }>({ name: "Alice", age: 30 }); console.log(myCollection.getItem("name")); // 输出: Alice console.log(myCollection.getItem("age")); // 输出: 30 // 错误使用:尝试添加非string类型的键 // myCollection.items[1] = "some value"; // TypeScript编译器会报错 ``` 在这个例子中,`KeyedCollection`接口定义了一个索引签名,允许对象有任意数量的属性,但所有属性的值都必须是同一类型(虽然在这个例子中我们使用`any`作为占位符)。然后,`Collection`类使用这个接口作为泛型约束,确保传入的`items`对象符合索引签名的要求。 #### 14.4 泛型约束与类类型 在某些情况下,我们可能想要约束泛型参数为某个类的实例或继承自某个类的子类。这可以通过在泛型约束中使用类类型来实现。 **示例:约束泛型参数为特定类的实例** ```typescript class Animal { name: string; constructor(name: string) { this.name = name; } speak(): void { console.log(`${this.name} makes a noise.`); } } class Dog extends Animal { bark(): void { console.log(`${this.name} barks.`); } } interface AnimalConstructor { new (name: string): Animal; } class AnimalFactory<T extends AnimalConstructor> { constructor(private animalClass: T) {} createAnimal(name: string): InstanceType<T> { return new this.animalClass(name); } } let dogFactory = new AnimalFactory<typeof Dog>(Dog); let myDog = dogFactory.createAnimal("Buddy"); myDog.speak(); // 输出: Buddy makes a noise. myDog.bark(); // 输出: Buddy barks. ``` 在这个例子中,`AnimalConstructor`接口定义了一个构造函数签名,它要求任何实现该接口的类型都必须有一个接受字符串参数并返回`Animal`或其子类的实例的构造函数。然后,`AnimalFactory`类使用这个接口作为泛型约束,允许其`createAnimal`方法返回由传入构造函数创建的实例的精确类型(通过`InstanceType<T>`工具类型获取)。 #### 结语 泛型类与泛型约束是TypeScript中强大的特性,它们不仅提高了代码的复用性和灵活性,还增强了类型安全性。通过合理应用泛型类与泛型约束,我们可以编写出更加健壮、易于维护和扩展的TypeScript代码。在实际项目开发中,深入理解并灵活运用这些特性,将极大地提升我们的开发效率和代码质量。
上一篇:
13 | 泛型(1):泛型函数与泛型接口
下一篇:
15 | 类型检查机制(1):类型推断
该分类下的相关小册推荐:
剑指TypeScript
TypeScript 全面进阶指南
TypeScript入门指南