TypeScript: TypeScript 泛型基础
最后更新:2026-08-26
泛型是 TypeScript 最强大的特性——它让你写"适用于多种类型"的代码,同时保持完整的类型安全。理解泛型,是掌握 TypeScript 的关键一步。
1. 为什么需要泛型
(1) 没有 泛型的困境
TYPESCRIPT
// 方案一:用 any——丢失类型信息
function firstAny(arr: any[]): any {
return arr[0];
}
let n = firstAny([1, 2, 3]); // n 是 any——不知道是 number
let s = firstAny(["a", "b"]); // s 是 any——不知道是 string
// 方案二:为每种类型写函数——代码重复
function firstNumber(arr: number[]): number { return arr[0]; }
function firstString(arr: string[]): string { return arr[0]; }
// 无穷无尽...
(2) 泛型的解决方案
TYPESCRIPT
function first<T>(arr: T[]): T {
return arr[0];
}
let n = first([1, 2, 3]); // T 推断为 number → 返回 number
let s = first(["a", "b"]); // T 推断为 string → 返回 string
let b = first([true, false]); // T 推断为 boolean → 返回 boolean
console.log(n.toFixed(2)); // ✅ TypeScript 知道 n 是 number
console.log(s.toUpperCase()); // ✅ TypeScript 知道 s 是 string
📌 核心思想:
<T> 是类型参数——调用时由传入的参数类型决定 T 是什么。一次定义,多种类型使用,且每种情况都有精确的类型信息。
2. 泛型函数
(1) 基本语法
TYPESCRIPT
function identity<T>(value: T): T {
return value;
}
// TypeScript 自动推断 T
let num = identity(42); // T = number
let str = identity("hello"); // T = string
// 也可以显式指定 T(有时需要)
let result = identity<string>("hello");
(2) 多个类型参数
TYPESCRIPT
function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
let p1 = pair("name", 42); // [string, number]
let p2 = pair(true, [1, 2, 3]); // [boolean, number[]]
(3) 泛型与数组
TYPESCRIPT
function map<T, U>(arr: T[], transform: (item: T) => U): U[] {
return arr.map(transform);
}
let numbers = [1, 2, 3];
let strings = map(numbers, n => `第${n}个`);
// T = number, U = string → 返回 string[]
console.log(strings); // ["第1个", "第2个", "第3个"]
▶ 示例:泛型工具函数
TYPESCRIPT
// 过滤数组
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
return arr.filter(predicate);
}
// 查找数组
function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
return arr.find(predicate);
}
// 数组分块
function chunk<T>(arr: T[], size: number): T[][] {
let result: T[][] = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
let nums = [1, 2, 3, 4, 5, 6, 7];
let evens = filter(nums, n => n % 2 === 0);
let found = find(nums, n => n > 5);
let groups = chunk(nums, 3);
console.log("偶数:" + evens); // "偶数:2,4,6"
console.log("找到:" + found); // "找到:6"
console.log("分块:" + JSON.stringify(groups)); // "[[1,2,3],[4,5,6],[7]]"
输出:
TEXT
📖 仅展示
偶数:2,4,6
找到:6
分块:[[1,2,3],[4,5,6],[7]]
3. 泛型接口
(1) 基本语法
TYPESCRIPT
interface Box<T> {
value: T;
}
let stringBox: Box<string> = { value: "hello" };
let numberBox: Box<number> = { value: 42 };
(2) 泛型接口描述函数
TYPESCRIPT
interface Transformer<A, B> {
(input: A): B;
}
let toString: Transformer<number, string> = (n) => String(n);
let toLength: Transformer<string, number> = (s) => s.length;
console.log(toString(42)); // "42"
console.log(toLength("hello")); // 5
(3) 泛型接口描述仓库模式
TYPESCRIPT
interface Repository<T> {
findById(id: string): T | null;
findAll(): T[];
save(item: T): void;
delete(id: string): boolean;
}
interface User {
id: string;
name: string;
}
class UserRepo implements Repository<User> {
private data: User[] = [];
findById(id: string): User | null {
return this.data.find(u => u.id === id) ?? null;
}
findAll(): User[] {
return this.data;
}
save(item: User): void {
this.data.push(item);
}
delete(id: string): boolean {
let len = this.data.length;
this.data = this.data.filter(u => u.id !== id);
return this.data.length < len;
}
}
4. 泛型类
(1) 基本语法
TYPESCRIPT
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
get size(): number {
return this.items.length;
}
}
let numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.push(3);
console.log(numStack.pop()); // 3
console.log(numStack.peek()); // 2
console.log(numStack.size); // 2
let strStack = new Stack<string>();
strStack.push("a");
strStack.push("b");
console.log(strStack.pop()); // "b"
(2) 泛型类的静态成员
TYPESCRIPT
class GenericClass<T> {
// static defaultValue: T; // ❌ 静态成员不能引用类的类型参数
// ✅ 静态方法需要自己的类型参数
static create<U>(value: U): GenericClass<U> {
let instance = new GenericClass<U>();
instance.value = value;
return instance;
}
constructor(public value: T) {}
}
let instance = GenericClass.create("hello"); // GenericClass<string>
📌 原因: 类的类型参数
T 是实例级别的——每个实例可以有不同的 T。静态成员属于类本身,不属于任何实例,所以不能引用实例的类型参数。
5. 泛型约束(extends)
默认情况下,泛型 T 可以是任何类型——有时这太宽泛了。用 extends 约束 T 必须满足某些条件:
(1) 约束为特定接口
TYPESCRIPT
interface HasLength {
length: number;
}
// T 必须有 length 属性
function logLength<T extends HasLength>(value: T): void {
console.log(`长度:${value.length}`);
}
logLength("hello"); // ✅ string 有 length
logLength([1, 2, 3]); // ✅ 数组有 length
logLength({ length: 10 }); // ✅ 对象有 length
// logLength(42); // ❌ number 没有 length
(2) 约束为另一个泛型
TYPESCRIPT
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let user = { name: "Charlie", age: 20, email: "xiao@example.com" };
console.log(getProperty(user, "name")); // ✅ "Charlie" —— K = "name"
console.log(getProperty(user, "age")); // ✅ 20 —— K = "age"
// getProperty(user, "phone"); // ❌ "phone" 不是 user 的键
(3) 约束为构造函数
TYPESCRIPT
function createInstance<T>(Constructor: new () => T): T {
return new Constructor();
}
class Dog {
bark() { return "汪!"; }
}
let dog = createInstance(Dog);
console.log(dog.bark()); // "汪!"
▶ 示例:类型安全的对象合并
TYPESCRIPT
function merge<T extends object, U extends object>(a: T, b: U): T & U {
return { ...a, ...b };
}
let defaults = { host: "localhost", port: 3000 };
let custom = { port: 8080, debug: true };
let config = merge(defaults, custom);
// 推断类型:{ host: string; port: number } & { port: number; debug: boolean }
// 简化后:{ host: string; port: number; debug: boolean }
console.log(config.host); // "localhost"
console.log(config.port); // 8080
console.log(config.debug); // true
6. 常用内置泛型类型
TypeScript 内置了许多实用的泛型工具类型:
| 类型 | 作用 | 示例 |
|---|---|---|
Array<T> |
数组类型 | Array<number> |
Promise<T> |
Promise 类型 | Promise<string> |
Record<K, V> |
键值对对象 | Record<string, number> |
Partial<T> |
所有属性可选 | Partial<Config> |
Required<T> |
所有属性必选 | Required<Config> |
Readonly<T> |
所有属性只读 | Readonly<Config> |
Pick<T, K> |
选取部分属性 | Pick<User, "name" | "age"> |
Omit<T, K> |
排除部分属性 | Omit<User, "email"> |
Exclude<T, U> |
从联合类型排除 | Exclude<"a"|"b"|"c", "a"> |
Extract<T, U> |
从联合类型提取 | Extract<"a"|"b"|"c", "a"|"b"> |
ReturnType<T> |
函数返回值类型 | ReturnType<typeof fn> |
Parameters<T> |
函数参数类型元组 | Parameters<typeof fn> |
TYPESCRIPT
// 实际使用示例
interface User {
id: number;
name: string;
email: string;
age: number;
}
// 创建用户时 id 不需要提供
type CreateUser = Omit<User, "id">;
// 更新用户时所有字段都可选
type UpdateUser = Partial<User>;
// 用户摘要只有部分字段
type UserSummary = Pick<User, "id" | "name">;
let newUser: CreateUser = { name: "Charlie", email: "xiao@example.com", age: 20 };
let update: UpdateUser = { age: 21 };
let summary: UserSummary = { id: 1, name: "Charlie" };
❓ 常见问题
Q 泛型类型参数用什么命名?
A 惯例用单个大写字母——T(Type)、U/V/W(后续类型)、K(Key)、V(Value)、E(Element)。多个单词时用 PascalCase:
TItem、TResponse。团队统一即可。Q 什么时候需要显式指定泛型类型?
A 大多数时候 TypeScript 能从参数推断泛型类型。需要显式指定的场景:(1) 函数没有参数或参数类型不能确定 T (2) 推断结果太宽泛(如推断为联合类型) (3) 函数返回类型需要更精确的控制。
Q 泛型约束 extends 和类继承 extends 有什么区别?
A 语法相同但语义不同。泛型约束
T extends HasLength 表示"T 至少要有 HasLength 的结构"——是类型兼容关系。类继承 class Dog extends Animal 表示"Dog 是 Animal 的子类"——是面向对象关系。Q
Record<string, number> 和 { [key: string]: number } 有什么区别?A 完全等价。Record 是内置的泛型工具类型,底层就是索引签名。
Record<string, number> 更简洁,{ [key: string]: number } 更灵活(可以加已知属性)。📖 小节
- 泛型
<T>是类型参数——让函数/接口/类适用于多种类型,同时保持类型安全 - 泛型函数:
function fn<T>(arg: T): T,T 由调用时的参数推断 - 泛型接口和泛型类:把 T 放在名字后面,
interface Box<T>、class Stack<T> - 泛型约束
T extends 类型限制 T 的范围,确保 T 满足某些条件 - TypeScript 内置了丰富的泛型工具类型:Record、Partial、Required、Readonly、Pick、Omit 等
📝 作业
- 基础题(难度⭐):写一个泛型函数
wrapInArray<T>(value: T): T[],把单个值包装成数组。测试 number 和 string 类型。 - 进阶题(难度⭐⭐):写一个泛型函数
pluck<T, K extends keyof T>(items: T[], key: K): T[K][],从对象数组中提取指定属性的值。例如pluck([{name: "a", age: 1}, {name: "b", age: 2}], "name")返回["a", "b"]。 - 挑战题(难度⭐⭐⭐):实现一个泛型类
DataStore<T extends { id: string }>,提供 save/find/delete 方法。id 属性通过泛型约束确保存在。创建DataStore<{ id: string; title: string }>实例测试增删查功能。