TypeScript 泛型
泛型(Generics)是 TypeScript 中非常重要的特性,它允许我们在定义函数、接口或类时不预先指定具体的类型,而在使用时再指定类型的一种特性。
为什么需要泛型?
问题场景
假设我们需要一个函数,可以返回任何类型的值:
ts
// 不使用泛型的方式
function getValue(arg: any): any {
return arg;
}
const value1 = getValue(123); // 类型是 any
const value2 = getValue('hello'); // 类型是 any这种方式的问题是:
- 失去了类型检查的优势
- 无法在编译时捕获类型错误
- 代码提示不准确
泛型的解决方案
使用泛型可以解决这些问题:
ts
function getValue<T>(arg: T): T {
return arg;
}
const value1 = getValue(123); // 类型是 number
const value2 = getValue('hello'); // 类型是 string泛型函数
基础语法
ts
// 函数名后添加 <T>,T 是类型变量(可以任意命名)
function identity<T>(arg: T): T {
return arg;
}
// 使用方式1:显式指定类型
const result1 = identity<string>('hello');
// 使用方式2:让 TypeScript 自动推断类型(推荐)
const result2 = identity(123); // 类型推断为 number多个泛型参数
ts
function map<I, O>(arr: I[], fn: (item: I) => O): O[] {
return arr.map(fn);
}
const numbers = [1, 2, 3];
const strings = map(numbers, item => item.toString()); // ['1', '2', '3']泛型约束(extends)
使用 extends 关键字来限制泛型参数的类型:
ts
// 限制 T 必须包含 length 属性
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
getLength('hello'); // ✅ 字符串有 length 属性
getLength([1, 2, 3]); // ✅ 数组有 length 属性
getLength({ length: 5 }); // ✅ 对象有 length 属性
getLength(123); // ❌ 数字没有 length 属性使用 keyof 约束
ts
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: 'Tom', age: 20 };
getProperty(person, 'name'); // ✅ 正确
getProperty(person, 'age'); // ✅ 正确
getProperty(person, 'sex'); // ❌ 错误:'sex' 不存在泛型默认值
ts
function createArray<T = string>(length: number, value: T): T[] {
return Array(length).fill(value);
}
createArray(3, 'hello'); // string[]
createArray<number>(3, 0); // number[]泛型接口
基础用法
ts
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
const myIdentity: GenericIdentityFn<number> = identity;泛型接口示例
ts
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
interface User {
id: number;
name: string;
}
// 使用泛型接口
const userResponse: ApiResponse<User> = {
code: 200,
message: 'success',
data: { id: 1, name: 'Tom' }
};多个泛型参数
ts
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair1: KeyValuePair<string, number> = { key: 'age', value: 20 };
const pair2: KeyValuePair<number, string> = { key: 1, value: 'Tom' };泛型类
基础用法
ts
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
const myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
const stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = '';
stringNumeric.add = function(x, y) { return x + y; };类中的泛型方法
ts
class DataStorage<T> {
private data: T[] = [];
addItem(item: T): void {
this.data.push(item);
}
getItem(index: number): T {
return this.data[index];
}
getAllItems(): T[] {
return this.data;
}
}
// 使用字符串类型的存储
const stringStorage = new DataStorage<string>();
stringStorage.addItem('hello');
stringStorage.addItem('world');
// 使用数字类型的存储
const numberStorage = new DataStorage<number>();
numberStorage.addItem(1);
numberStorage.addItem(2);泛型类型别名
ts
type Nullable<T> = T | null;
type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Partial<T> = { [P in keyof T]?: T[P] };
// 使用
type User = {
id: number;
name: string;
};
type NullableUser = Nullable<User>; // User | null
type ReadonlyUser = Readonly<User>; // { readonly id: number; readonly name: string; }映射类型中的泛型
ts
// 将对象的所有属性转换为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 将对象的所有属性转换为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 从类型中选择指定的属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 从类型中排除指定的属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
// 使用示例
interface User {
id: number;
name: string;
age: number;
}
type PartialUser = Partial<User>; // { id?: number; name?: string; age?: number; }
type ReadonlyUser = Readonly<User>; // { readonly id: number; readonly name: string; readonly age: number; }
type UserName = Pick<User, 'name'>; // { name: string; }
type UserWithoutAge = Omit<User, 'age'>; // { id: number; name: string; }内置工具类型
Partial<T>
将所有属性变为可选:
ts
interface User {
id: number;
name: string;
}
type PartialUser = Partial<User>;
// 等价于 { id?: number; name?: string; }Required<T>
将所有属性变为必需:
ts
interface User {
id?: number;
name?: string;
}
type RequiredUser = Required<User>;
// 等价于 { id: number; name: string; }Readonly<T>
将所有属性变为只读:
ts
interface User {
id: number;
name: string;
}
type ReadonlyUser = Readonly<User>;
// 等价于 { readonly id: number; readonly name: string; }Pick<T, K>
从类型中选择指定的属性:
ts
interface User {
id: number;
name: string;
age: number;
}
type UserName = Pick<User, 'name'>;
// 等价于 { name: string; }Omit<T, K>
从类型中排除指定的属性:
ts
interface User {
id: number;
name: string;
age: number;
}
type UserWithoutAge = Omit<User, 'age'>;
// 等价于 { id: number; name: string; }Record<K, T>
创建一个对象类型:
ts
type UserRecord = Record<string, number>;
// 等价于 { [key: string]: number; }
const users: Record<'admin' | 'user', { id: number; name: string }> = {
admin: { id: 1, name: 'Admin' },
user: { id: 2, name: 'User' }
};Exclude<T, U>
从类型中排除指定的类型:
ts
type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T1 = Exclude<string | number, number>; // stringExtract<T, U>
从类型中提取指定的类型:
ts
type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'
type T1 = Extract<string | number, number>; // numberNonNullable<T>
从类型中排除 null 和 undefined:
ts
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]ReturnType<T>
获取函数返回值的类型:
ts
function fn() {
return { x: 10, y: 20 };
}
type T = ReturnType<typeof fn>; // { x: number; y: number; }Parameters<T>
获取函数参数的类型:
ts
function fn(a: number, b: string) {
return a + b;
}
type T = Parameters<typeof fn>; // [number, string]条件类型
基础语法
ts
// T extends U ? X : Y
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type T0 = TypeName<string>; // "string"
type T1 = TypeName<number>; // "number"
type T2 = TypeName<boolean>; // "boolean"infer 关键字
infer 用于在条件类型中推断类型:
ts
// 获取数组元素的类型
type ArrayElementType<T> = T extends (infer U)[] ? U : never;
type T0 = ArrayElementType<string[]>; // string
type T1 = ArrayElementType<number[]>; // number
type T2 = ArrayElementType<(string | number)[]>; // string | number
// 获取函数返回值的类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// 获取函数参数的第一个参数类型
type FirstParameter<T> = T extends (arg: infer P, ...args: any[]) => any ? P : never;分布式条件类型
当条件类型作用于联合类型时,会进行分布式计算:
ts
type ToArray<T> = T extends any ? T[] : never;
type StrArrOrNumArr = ToArray<string | number>;
// 等价于 string[] | number[]
// 而不是 (string | number)[]实际应用场景
1. API 响应类型
ts
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
interface User {
id: number;
name: string;
}
async function fetchUser(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`/api/user/${id}`);
return response.json();
}2. 缓存函数
ts
function memoize<T extends (...args: any[]) => any>(
fn: T
): (...args: Parameters<T>) => ReturnType<T> {
const cache = new Map();
return (...args: Parameters<T>): ReturnType<T> => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
}3. 事件处理
ts
type EventHandler<T> = (event: T) => void;
interface MouseEvent {
x: number;
y: number;
}
interface KeyboardEvent {
key: string;
}
class EventEmitter<T> {
private handlers: EventHandler<T>[] = [];
addHandler(handler: EventHandler<T>): void {
this.handlers.push(handler);
}
emit(event: T): void {
this.handlers.forEach(handler => handler(event));
}
}
const mouseEmitter = new EventEmitter<MouseEvent>();
mouseEmitter.addHandler(event => console.log(`Mouse at (${event.x}, ${event.y})`));4. 工厂模式
ts
interface Factory<T> {
create(): T;
}
class UserFactory implements Factory<User> {
create(): User {
return { id: 1, name: 'Tom' };
}
}
function createInstance<T>(factory: Factory<T>): T {
return factory.create();
}
const user = createInstance(new UserFactory());最佳实践
使用有意义的泛型参数名:
T:单个类型K:键类型V:值类型E:元素类型
使用约束提高类型安全:
tsfunction getLength<T extends { length: number }>(arg: T): number { return arg.length; }合理使用默认类型:
tsinterface Config<T = string> { value: T; }避免过度使用泛型:
- 不是所有情况都需要泛型
- 优先考虑代码的可读性
利用类型推断:
- 让 TypeScript 自动推断类型,而不是显式指定
最后更新:2025年