泛型(Generics)
泛型是 TypeScript 最强大的特性之一,允许编写可重用、类型安全的代码。
泛型基础
为什么需要泛型
// 问题:使用 any 会丢失类型信息
function identity(value: any): any {
return value;
}
const result = identity("hello"); // any,丢失了 string 类型
// 解决:使用泛型保留类型信息
function identityGeneric<T>(value: T): T {
return value;
}
const result2 = identityGeneric("hello"); // string,类型被保留基本语法
// 泛型函数
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// 显式指定类型
first<number>([1, 2, 3]); // number | undefined
// 类型推断
first([1, 2, 3]); // number | undefined
first(["a", "b"]); // string | undefined多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
const swapped = swap([1, "hello"]); // [string, number]
// 常见命名约定
// T - Type(类型)
// K - Key(键)
// V - Value(值)
// E - Element(元素)
// R - Return(返回值)泛型约束
extends 约束
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(value: T): T {
console.log(value.length);
return value;
}
logLength("hello"); // ✅ string 有 length
logLength([1, 2, 3]); // ✅ 数组有 length
logLength({ length: 10 }); // ✅ 有 length 属性
// logLength(123); // ❌ number 没有 lengthkeyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 25 };
getProperty(user, "name"); // string
getProperty(user, "age"); // number
// getProperty(user, "email"); // ❌ "email" 不是 user 的键多重约束
interface Printable {
print(): void;
}
interface Loggable {
log(): void;
}
function process<T extends Printable & Loggable>(value: T): void {
value.print();
value.log();
}泛型接口
// 泛型接口
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}
// 实现泛型接口
class Box<T> implements Container<T> {
constructor(public value: T) {}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
const numberBox = new Box<number>(42);
const stringBox = new Box<string>("hello");泛型接口作为函数类型
interface Comparator<T> {
(a: T, b: T): number;
}
const numberComparator: Comparator<number> = (a, b) => a - b;
const stringComparator: Comparator<string> = (a, b) => a.localeCompare(b);泛型类
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];
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.pop(); // 2
const stringStack = new Stack<string>();
stringStack.push("hello");泛型类的静态成员
class GenericClass<T> {
// 静态成员不能使用类的泛型参数
// static value: T; // ❌ 错误
// 静态成员可以有自己的泛型
static create<U>(value: U): GenericClass<U> {
const instance = new GenericClass<U>();
instance.value = value;
return instance;
}
value!: T;
}泛型类型别名
// 简单泛型类型
type Nullable<T> = T | null;
type Optional<T> = T | undefined;
let name: Nullable<string> = null;
name = "Alice";
// 复杂泛型类型
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function divide(a: number, b: number): Result<number, string> {
if (b === 0) {
return { success: false, error: "Division by zero" };
}
return { success: true, data: a / b };
}泛型默认类型
interface Response<T = unknown> {
data: T;
status: number;
}
// 使用默认类型
const response1: Response = { data: "hello", status: 200 };
// 指定具体类型
const response2: Response<{ name: string }> = {
data: { name: "Alice" },
status: 200
};带约束的默认类型
interface Container<T extends object = { id: number }> {
data: T;
}
const c1: Container = { data: { id: 1 } }; // 使用默认类型
const c2: Container<{ name: string }> = { data: { name: "test" } };内置工具类型
TypeScript 提供了许多基于泛型的工具类型:
Partial
将所有属性变为可选:
interface User {
name: string;
age: number;
email: string;
}
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string }
function updateUser(user: User, updates: Partial<User>): User {
return { ...user, ...updates };
}Required
将所有属性变为必选:
interface Config {
url?: string;
timeout?: number;
}
type RequiredConfig = Required<Config>;
// { url: string; timeout: number }Readonly
将所有属性变为只读:
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; readonly email: string }Pick<T, K>
选取指定属性:
type UserBasic = Pick<User, "name" | "email">;
// { name: string; email: string }Omit<T, K>
排除指定属性:
type UserWithoutEmail = Omit<User, "email">;
// { name: string; age: number }Record<K, V>
创建键值对类型:
type UserRoles = Record<string, string[]>;
// { [key: string]: string[] }
const roles: UserRoles = {
admin: ["read", "write", "delete"],
user: ["read"]
};Extract<T, U> 和 Exclude<T, U>
type T = "a" | "b" | "c";
type Extracted = Extract<T, "a" | "b">; // "a" | "b"
type Excluded = Exclude<T, "a">; // "b" | "c"NonNullable
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // stringReturnType 和 Parameters
function greet(name: string, age: number): string {
return `${name} is ${age}`;
}
type GreetReturn = ReturnType<typeof greet>; // string
type GreetParams = Parameters<typeof greet>; // [string, number]高级泛型模式
条件泛型
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<number[]>; // true
type B = IsArray<string>; // false
// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;
type C = ElementType<number[]>; // number
type D = ElementType<string[]>; // string分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
// 联合类型会被分布处理
type Result = ToArray<string | number>;
// string[] | number[](不是 (string | number)[])映射泛型
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }最佳实践
- 使用描述性类型参数名:复杂场景使用
TData、TResult等 - 适当使用约束:限制泛型范围,提供更好的类型提示
- 提供默认类型:让 API 更易用
- 避免过度泛型化:简单场景不需要泛型
- 组合工具类型:复用 TypeScript 内置类型
// 推荐:组合使用工具类型
type UpdatePayload<T> = Partial<Omit<T, "id" | "createdAt">>;
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
type UserUpdate = UpdatePayload<User>;
// { name?: string; email?: string }