1. 引言
在上一篇学习笔记中,我们介绍了 TypeScript 的基础知识,包括类型注解、接口、类和函数。本篇将深入探讨 TypeScript 的类型系统,学习如何使用高级类型、泛型和类型推断等特性,以便在复杂应用场景中更好地利用 TypeScript。
2. 高级类型
2.1 交叉类型
交叉类型(Intersection Types)用于将多个类型合并为一个类型,它表示一个对象可以同时具有这些类型的所有成员。
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: number;
}
type PersonEmployee = Person & Employee;
let personEmployee: PersonEmployee = {
name: "John",
age: 30,
employeeId: 12345
};
2.2 联合类型
联合类型(Union Types)表示一个值可以是几种类型之一。使用竖线 (|
) 分隔不同的类型。
let value: string | number;
value = "Hello";
value = 42;
function format(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}
2.3 类型别名
类型别名(Type Aliases)用于为类型创建一个新名字,可以是基础类型、联合类型、交叉类型甚至是对象类型。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === "string") {
return n;
}
return n();
}
3. 泛型
泛型(Generics)允许我们在定义函数、接口或类时不预先指定具体的类型,而是在使用时再指定类型,从而提高代码的通用性和可重用性。
3.1 泛型函数
泛型函数可以在函数名后面添加一个尖括号来定义泛型参数。
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity<string>("myString");
let output2 = identity<number>(100);
3.2 泛型接口
接口也可以使用泛型来定义。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
3.3 泛型类
类也可以使用泛型来定义。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;
3.4 泛型约束
有时候我们希望限制泛型的类型,这时候可以使用泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// 正确
loggingIdentity({ length: 10, value: 3 });
// 错误,类型没有 length 属性
// loggingIdentity(3);
4. 类型推断
TypeScript 会根据代码的上下文自动推断类型,从而减少显式的类型注解。
let x = 3; // x 被推断为 number 类型
function add(a: number, b: number) {
return a + b; // 返回值被推断为 number 类型
}
let result = add(2, 5); // result 被推断为 number 类型
5. 类型守卫
类型守卫(Type Guards)用于在运行时检查类型,从而在代码中进行类型保护。
function isNumber(x: any): x is number {
return typeof x === "number";
}
function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
6. 映射类型
映射类型(Mapped Types)用于根据已有类型创建新的类型。它们经常与类型别名和索引签名一起使用。
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface Person {
name: string;
age: number;
}
let readonlyPerson: Readonly<Person> = {
name: "John",
age: 30
};
// readonlyPerson.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.
7. 实用工具类型
TypeScript 提供了一些实用工具类型,用于处理类型操作。
-
Partial<T>
:将类型 T 的所有属性设为可选。 -
Required<T>
:将类型 T 的所有属性设为必选。 -
Readonly<T>
:将类型 T 的所有属性设为只读。 -
Pick<T, K>
:从类型 T 中选择部分属性构成新的类型。 -
Omit<T, K>
:从类型 T 中排除部分属性构成新的类型。
interface Person {
name: string;
age: number;
address?: string;
}
let partialPerson: Partial<Person> = {
name: "John"
};
let requiredPerson: Required<Person> = {
name: "John",
age: 30,
address: "123 Main St"
};
let readonlyPerson: Readonly<Person> = {
name: "John",
age: 30
};
// readonlyPerson.age = 31; // Error
let pickedPerson: Pick<Person, "name" | "age"> = {
name: "John",
age: 30
};
let omittedPerson: Omit<Person, "address"> = {
name: "John",
age: 30
};
8. 总结
在本篇学习笔记中,我们深入探讨了 TypeScript 的高级类型系统,包括交叉类型、联合类型、类型别名、泛型、类型推断、类型守卫、映射类型和实用工具类型。通过这些高级特性,我们可以编写出更强大、灵活和可维护的代码。
下一篇学习笔记将介绍 TypeScript 中的模块系统和命名空间,以及如何与 JavaScript 进行互操作。希望你能继续关注本系列的学习笔记,进一步提升 TypeScript 编程技能。