1,介绍
泛型(Generics)是 TypeScript 的一项强大的语言特性,它允许我们编写可以重用的代码,从而提高代码的可重用性、可维护性和灵活性。
简单来说,泛型就是将类型作为参数来定义一种通用的类型或函数的能力。通过泛型,我们可以定义一种函数、类或接口,其具体的类型是在使用时确定的,而不是在定义时确定的。这使得我们能够编写更灵活、更通用的代码,而不需要针对每种具体类型编写重复的代码。
简单的泛型函数示例:
function identity<T>(arg: T): T {
return arg;
}
函数 identity
接收一个泛型参数 T,表示输入的参数类型和返回值的类型。
在调用该函数时,我们可以使用不同的类型参数来调用该函数,从而得到不同的输出结果。例如:
// 不同的返回值类型
let output1 = identity<string>("Hello, world!");
let output2 = identity<number>(42);
除了函数,还可以使用泛型来定义类和接口。例如,下面是一个使用泛型的简单类示例:
class Box<T> {
constructor(value: T) {
this.contents = value;
}
contents: T;
}
Box
类接收一个泛型类型参数 T,并在类中使用该参数来定义 contents
属性的类型。在类的构造函数中,我们可以将泛型类型参数 T 传递给构造函数,并将其赋值给 contents 属性。
let box1 = new Box<string>("Hello, world!");
let box2 = new Box<number>(42);
2,自定义泛型类型
在 TypeScript 中,使用 <>
来定义泛型类型参数,并在函数、类、接口或类型别名的定义中使用它们。
例如,下面的 Pair
类型表示一个由两个元素组成的键值对,其中键和值可以是任意类型:
type Pair<K, V> = {
key: K;
value: V;
};
在上面的代码中,使用了2个泛型类型参数 K 和 V 来表示键和值的类型。
通过定义这个泛型类型,得到一个类型安全的键值对,其中键和值可以是任意类型。
const person: Pair<string, number> = {
key: "age",
value: 30,
};
示例2
type Result<T> = {
success: boolean;
data: T;
};
function getResult<T>(data: T): Result<T> {
return { success: true, data };
}
在上面的代码中,我们使用一个泛型类型参数 T
来表示 Result
类型中的 data
属性的类型。
getResult
函数可以接受任何类型的参数,并返回一个包含 success 和 data 属性的对象。
3,泛型约束
在 TypeScript 中,泛型约束是一种非常有用的特性,可以对泛型类型参数进行限制,从而增强代码的类型安全性和可读性。
可以使用下面的关键字实现泛型约束。(extends, keyof,infer最常见)
1,extends
通过使用 extends 关键字,可以对泛型类型参数进行更精确的限制,使得在使用泛型时获得更准确的类型推断和类型检查。
使用 extends 关键字来指定泛型类型参数时,泛型类型参数有2个特点
- 必须是某个类型的子类型
- 或者必须满足某个接口的约束条件。
举例,必须是某个类型的子类型:
function printLength<T extends Array<any>>(arr: T): void {
console.log(arr.length);
}
printLength([1, 2, 3]); // 输出 3
printLength(["a", "b", "c"]); // 输出 3
printLength(123); // 类型错误,必须是数组类型
在上面的示例中,定义了一个泛型函数 printLength
,它接受一个数组类型的参数 arr
。
而使用 extends Array 泛型约束,我们限制了泛型类型参数 T 必须是 Array 的子类型。这意味着 arr 参数必须是一个数组类型,否则会产生类型错误。
举例,必须满足某个接口的约束条件:
interface HasLength {
length: number;
}
function printLength<T extends HasLength>(obj: T): void {
console.log(obj.length);
}
printLength([1, 2, 3]); // 输出 3
printLength("hello"); // 输出 5
printLength({ length: 10 }); // 输出 10
printLength(123); // 类型错误,必须满足 HasLength 接口的约束条件
在上面的示例中,定义了一个接口 HasLength
,它包含一个 length 属性。
然后又定义了一个泛型函数 printLength
,它接受一个满足 HasLength
接口约束的参数 obj。
使用 extends HasLength 泛型约束,我们限制了泛型类型参数 T 必须满足 HasLength 接口的约束条件,即必须包含 length 属性。只有满足约束条件的参数才能通过类型检查。
2,keyof
keyof 是 TypeScript 中的一个索引类型查询操作符,用于获取一个类型的所有属性名组成的联合类型。它可以用于限制泛型类型参数只能是某个对象的属性名。
也就是说,keyof T 表示获取类型 T 的所有属性名组成的联合类型。
type Person = {
name: string;
age: number;
};
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person: Person = {
name: "John",
age: 30,
};
const name = getProperty(person, "name"); // 类型为 string
const age = getProperty(person, "age"); // 类型为 number
3,infer
在 TypeScript 中,infer 是一个用于条件类型(Conditional Types)中的关键字。
它用于从一个类型表达式中推断出一个新的类型,并将其作为条件类型的结果。例如,infer U
表示从某个类型中推断出一个新类型 U
。
当 infer
与条件类型结合使用时,可以从泛型类型参数中捕获具体的类型信息,然后将其赋值给一个新的类型变量。这允许我们在定义泛型类型时根据输入类型推断出其他类型的值,实现更灵活和复杂的类型操作。
示例1:提取函数返回类型:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greet(): string {
return "Hello, world!";
}
type Greeting = ReturnType<typeof greet>; // 类型为 string
在这个示例中,我们定义了一个 ReturnType 类型,它接受一个函数类型 T 作为参数。使用条件类型 T extends (...args: any[]) => infer R ? R : never
,我们检查泛型类型参数 T 是否是一个函数类型,如果是的话,就将函数返回值类型 R 赋值给 infer R,否则返回 never 类型。
换句话说,假设原来的函数返回值类型是 x,则
infer R
表示:使用infer
关键字将 x 捕获并赋值给泛型变量 R。
通过使用 typeof greet
来获取 greet 函数的类型,并将这个类型作为泛型参数传递给 ReturnType
类型,我们可以获得 greet 函数的返回值类型,即 string。
示例2:提取数组元素类型:
type ElementType<T> = T extends Array<infer E> ? E : never;
type Numbers = [1, 2, 3, 4, 5];
type Element = ElementType<Numbers>; // 类型为 1 | 2 | 3 | 4 | 5
在这个示例中,我们定义了一个 ElementType
类型,它接受一个数组类型 T 作为参数。
使用条件类型 T extends Array<infer E> ? E : never
,我们检查泛型类型参数 T 是否是一个数组类型,如果是的话,就将数组元素类型 E 赋值给 infer E,否则返回 never 类型。
通过将数组类型[1, 2, 3, 4, 5]
作为泛型参数传递给 ElementType
类型,我们可以推断出数组元素的类型为 1 | 2 | 3 | 4 | 5
。
4,Partial
用于将一个类型的所有属性变为可选属性。例如,Partial<T>
表示将 T 类型的所有属性变为可选属性。
举例,
type Person = {
name: string;
age: number;
};
function updatePerson(person: Person, update: Partial<Person>): void {
Object.assign(person, update);
}
const person: Person = {
name: "John",
age: 30,
};
updatePerson(person, { name: "Alice" }); // 第2个参数可以只传 name 属性
5,Required
用于将一个类型的所有属性变为必选属性。例如,Required<T>
表示将 T 类型的所有属性变为必选属性。
举例
type Person = {
name?: string;
age?: number;
};
function validatePerson(person: Required<Person>): boolean {
return person.name !== undefined && person.age !== undefined;
}
const person: Person = {
name: "John",
};
validatePerson(person); // 类型错误,缺少必选属性 age
6,Exclude
用于从一个联合类型中排除指定的类型,并生成一个新的联合类型。
Exclude 类型接受两个参数:原始类型和要排除的类型。它会返回一个新的联合类型,该类型包含了原始类型中除去指定类型的所有其他类型。
例如,Exclude<T, U>
表示从 T 类型中排除 U 类型的属性。
定义:
type Exclude<T, U> = T extends U ? never : T;
可以看到,Exclude 类型使用了条件类型,通过判断原始类型 T 是否可以赋值给要排除的类型 U,来决定是否将 T 包含在结果类型中。如果 T 可以赋值给 U,则返回 never 类型,否则返回 T 类型本身。
因为 T 是联合类型,所以会逐个判断是否可以赋值给类型 U
示例
type Colors = 'red' | 'green' | 'blue'; // 字符串字面量类型
type ExcludedColors = Exclude<Colors, 'green'>; // 'red'| 'blue';
const color: ExcludedColors = 'red';
console.log(color); // 输出:'red'
与 Omit
类型相比,Exclude
类型的作用更加通用,它可以用于排除任意类型,而不仅限于对象属性。
Omit
类型则专门用于排除对象的属性。
示例2:排除对象的属性
其实是将对象属性通过 keyof 转换为联合类型。
type Person = {
name: string;
age: number;
gender: string;
};
type ExcludeGender = Exclude<keyof Person, "gender">; // 类型为 "name" | "age"
function getPersonInfo(person: Person, key: ExcludeGender): string {
return person[key].toString();
}
const person: Person = {
name: "John",
age: 30,
gender: "male",
};
const name = getPersonInfo(person, "name"); // 类型为 string
const age = getPersonInfo(person, "age"); // 类型为 number
7,Pick
Pick:用于从一个类型中选取指定的属性。例如,Pick<T, K>
表示从 T 类型中选取类型 K 的属性。
举例
type Person = {
name: string;
age: number;
gender: string;
};
type PersonInfo = Pick<Person, "name" | "age">; // 类型为 { name: string; age: number; }
function getPersonInfo(person: Person, info: keyof PersonInfo): string {
return person[info].toString();
}
const person: Person = {
name: "John",
age: 30,
gender: "male",
};
const name = getPersonInfo(person, "name"); // 类型为 string
const age = getPersonInfo(person, "age"); // 类型为 number
8,Omit
用于从一个类型中排除指定的属性,并生成一个新的类型。
Omit 类型接受两个参数:原始类型和要排除的属性。它会返回一个新的类型,该类型包含了原始类型的所有属性,但排除了指定的属性。
定义:
Copy code
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
可以看到,Omit 类型内部使用了 Pick 和 Exclude 类型。
与 Exclude 类型相比,Omit 类型的作用更为具体,它专门用于从一个类型(接口或对象)中排除指定的属性。
示例
interface Person {
name: string;
age: number;
email: string;
}
type PersonWithoutEmail = Omit<Person, 'email'>; // { name: string, age: number }
const person: PersonWithoutEmail = {
name: 'John',
age: 25,
};
console.log(person); // 输出:{ name: 'John', age: 25 }
这些泛型约束类型可以灵活地组合使用,从而构建出更加复杂的类型约束。需要注意的是,使用泛型约束时要避免过度约束,否则可能会影响代码的可扩展性和可维护性。