文章目录
- 前言
- 泛型类型
- 链接关系
- K、T、V 含义
- 自动类型推断
- 泛型的应用场景
- 容器类和数据结构
- 函数和方法
- 接口和类
- 类型约束和扩展
- 常用的工具类型
前言
在 TypeScript
的泛型里经常会碰到一些字母,比如 K
、T
、V
,是不是觉得很奇怪?
泛型类型
图中的 T
称为泛型类型参数,它是我们希望传递给函数的类型占位符
。
使用泛型类型参数可以使函数、类或接口在处理不同类型的数据时变得更加灵活
和通用
。当我们定义泛型类型参数
时,它会在函数、类或接口中作为一个占位符,表示可以接受任意类型
的值。
链接关系
就像传递参数一样,我们获取用户指定的实际类型
并将其链接
到参数类型
和返回值类型
。
K、T、V 含义
那么是什么T
意思呢?图中的泛型类型参数T代表Type
,实际上T可以替换为任何有效的名称
。除了 之外T
,常见的泛型变量还有 K
、V
、E
等。
- K(Key):表示对象中key的类型
- V(Value):表示对象中值的类型
- E(Element):表示元素类型
K 和 V:
// 定义一个泛型接口,表示键值对
interface KeyValuePair<K, V> {key: K;value: V;
}// 使用 K 和 V 分别表示键和值的类型
let pair1: KeyValuePair<string, number> = { key: "age", value: 25 };
let pair2: KeyValuePair<number, boolean> = { key: 1, value: true };
E实现一个简单的队列(Queue)数据结构:
// 定义一个泛型队列类
class Queue<E> {private elements: E[] = [];// 将元素添加到队列末尾enqueue(element: E) {this.elements.push(element);}// 从队列头部移除并返回元素dequeue(): E | undefined {return this.elements.shift();}// 返回队列中的所有元素getAll(): E[] {return this.elements;}
}// 使用泛型队列存储数字
const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
numberQueue.enqueue(3);
console.log(numberQueue.getAll()); // Output: [1, 2, 3]
console.log(numberQueue.dequeue()); // Output: 1// 使用泛型队列存储字符串
const stringQueue = new Queue<string>();
stringQueue.enqueue("apple");
stringQueue.enqueue("banana");
console.log(stringQueue.getAll()); // Output: ['apple', 'banana']
console.log(stringQueue.dequeue()); // Output: "apple"
当然,不必只定义一个类型参数,可以引入任意数量
的类型参数
。这里我们引入了一个新的类型参数U
,它扩展了我们定义的函数。
自动类型推断
在调用identity函数时,我们可以显式指定
泛型参数的实际类型
。
当然,你也可以不指定
泛型参数的类型,让TypeScript自动帮我们完成类型推断
。
看完上面的动画,你是否已经了解泛型类型参数了?
泛型的应用场景
泛型在许多场景下都非常有用。下面是一些常见的泛型应用场景:
容器类和数据结构
泛型可以用于创建容器类
和数据结构
,如数组、链表、栈和队列等。通过使用泛型,我们可以在这些数据结构中存储和操作不同类型的数据,而不需要为每种类型都编写单独的实现。
// 使用泛型创建一个通用的数组栈
class Stack<T> {private items: T[] = [];push(item: T) {this.items.push(item);}pop(): T | undefined {return this.items.pop();}
}// 使用字符串类型的栈
const stringStack = new Stack<string>();
stringStack.push("Hello");
stringStack.push("World");
console.log(stringStack.pop()); // Output: "World"// 使用数字类型的栈
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // Output: 2
函数和方法
泛型函数和方法可以适用于多种类型
的参数
和返回值
。这使得我们可以编写更加通用和灵活的函数,而不需要为每种类型都编写重复的代码。
// 泛型函数用于反转数组
function reverseArray<T>(array: T[]): T[] {return array.reverse();
}const numbers = [1, 2, 3, 4, 5];
const reversedNumbers = reverseArray(numbers); // Output: [5, 4, 3, 2, 1]const strings = ["apple", "banana", "orange"];
const reversedStrings = reverseArray(strings); // Output: ["orange", "banana", "apple"]
接口和类
泛型还可以用于定义接口和类。通过使用泛型,我们可以创建可重用的接口和类,以适应不同类型的数据。这在编写通用的数据结构、算法和组件时非常有用。
使用泛型类型参数的接口:
// 泛型接口定义一个通用的 Pair 类型
interface Pair<T, U> {first: T;second: U;
}// 使用字符串和数字的 Pair
const pair1: Pair<string, number> = { first: "one", second: 1 };
const pair2: Pair<number, boolean> = { first: 42, second: true };
使用泛型类型参数的类:
class Box<T> {private value: T;constructor(value: T) {this.value = value;}getValue(): T {return this.value;}setValue(value: T) {this.value = value;}
}let box = new Box<string>("Hello");
console.log(box.getValue()); // output: "Hello"box.setValue("World");
console.log(box.getValue()); // output: "World"
类型约束和扩展
泛型还可以与类型约束
和扩展一起使用,以限制泛型参数的类型范围或添加特定的行为。通过使用类型约束,我们可以确保泛型参数具有特定的属性或方法。这提供了更强的类型安全性和代码可读性。
// 使用类型约束确保泛型参数具有 length 属性
function getLength<T extends { length: number }>(obj: T): number {return obj.length;
}console.log(getLength("Hello")); // Output: 5
console.log(getLength([1, 2, 3])); // Output: 3
console.log(getLength({ length: 10, width: 5 })); // Output: 10// 使用类型约束确保泛型参数为数字类型
function sum<T extends number>(a: T, b: T): T {return a + b;
}console.log(sum(3, 4)); // Output: 7
console.log(sum(2, "test")); // Error: Argument of type '"test"' is not assignable to parameter of type 'number'.
常用的工具类型
为了方便开发者 TypeScript 内置了一些常用的工具类型:
Partial:将类型 T 中的所有属性变为可选属性。
interface User {name: string;age: number;email: string;
}function updateUser(user: Partial<User>, newName: string): User {return { ...user, name: newName } as User;
}const user: User = {name: "Alice",age: 30,email: "alice@example.com",
};const updatedUser = updateUser({ age: 35 }, "Bob");
console.log(updatedUser);
Required:将类型 T 中的所有属性变为必选属性。
interface Props {name?: string;age?: number;
}function printInfo(info: Required<Props>) {console.log(info.name.toUpperCase(), info.age.toFixed(2));
}printInfo({ name: "John", age: 30 });
Readonly:将类型 T 中的所有属性变为只读属性。
interface Point {x: number;y: number;
}const point: Readonly<Point> = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property
Record<K, T>:创建一个具有指定键类型 K 和值类型 T 的对象类型
type Fruit = "apple" | "banana" | "orange";
const prices: Record<Fruit, number> = {apple: 1.5,banana: 2,orange: 1,
};
Pick<T, K>:从类型 T 中选择部分属性 K 组成新类型。
interface Person {name: string;age: number;address: string;
}type PersonInfo = Pick<Person, "name" | "age">;
// PersonInfo: { name: string; age: number; }
keyof: 获取给定类型的所有键(属性名)的联合类型。
interface Person {name: string;age: number;email: string;
}type PersonKeys = keyof Person;// PersonKeys 类型为 "name" | "age" | "email"