在 TypeScript 开发中,你是否经常遇到这样的困境:相似的逻辑需要为不同类型重复编写代码?或是使用
any类型后丢失了类型安全?泛型正是解决这些痛点的利器!本文将带你深入掌握泛型的应用技巧。
一、泛型:类型系统的函数参数
想象一下,你要写一个函数,既能处理数字也能处理字符串。没有泛型前,你可能会这样写:
typescript
// 处理数字
function processNumber(num: number): number {
return num * 2;
}
// 处理字符串
function processString(str: string): string {
return str.toUpperCase();
}
这种重复代码既冗余又难以维护。泛型 的出现让代码可以像函数接收参数一样接收类型:
typescript
// 泛型解决方案
function processValue<T>(value: T): T {
// 处理逻辑...
return value;
}
泛型核心概念
- 类型变量:
<T>中的 T 是类型变量,类似函数参数 - 类型参数化:调用时指定具体类型,如
processValue<number>(10) - 类型保留:输入类型和输出类型保持一致
二、泛型函数:编写灵活的工具函数
基础应用:创建重复元素的数组
typescript
function createRepeatArray<T>(value: T, count: number): T[] {
return Array(count).fill(value);
}
// 使用
const numbers = createRepeatArray(5, 3); // 推断为 number[]
// [5, 5, 5]
const strings = createRepeatArray('hello', 2); // 推断为 string[]
// ['hello', 'hello']
多个类型参数:处理不同类型组合
typescript
function createPair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
// 使用
const userRole = createPair('Alice', 'Admin'); // [string, string]
const userAge = createPair('Bob', 30); // [string, number]
三、泛型接口:构建通用数据模型
API 响应通用结构
typescript
// 定义通用API响应接口
interface ApiResponse<T> {
success: boolean;
code: number;
data: T;
message?: string;
}
// 用户数据接口
interface User {
id: number;
name: string;
email: string;
}
// 使用
const userResponse: ApiResponse<User> = {
success: true,
code: 200,
data: {
id: 1,
name: 'Alice',
email: 'alice@example.com'
}
};
// 产品数据接口
interface Product {
id: number;
name: string;
price: number;
}
const productResponse: ApiResponse<Product> = {
success: true,
code: 200,
data: {
id: 101,
name: 'Laptop',
price: 1299
}
};
CRUD 操作通用接口
typescript
// 通用CRUD接口
interface Repository<T> {
getAll(): T[];
getById(id: number): T | undefined;
create(item: T): void;
update(id: number, item: Partial<T>): boolean;
delete(id: number): boolean;
}
// 用户实体实现
class UserRepository implements Repository<User> {
private users: User[] = [];
getAll(): User[] {
return [...this.users];
}
getById(id: number): User | undefined {
return this.users.find(user => user.id === id);
}
create(user: User): void {
this.users.push(user);
}
update(id: number, updates: Partial<User>): boolean {
const user = this.getById(id);
if (!user) return false;
Object.assign(user, updates);
return true;
}
delete(id: number): boolean {
const index = this.users.findIndex(user => user.id === id);
if (index === -1) return false;
this.users.splice(index, 1);
return true;
}
}
四、泛型类:创建灵活的数据容器
通用缓存系统
typescript
class Cache<T> {
private data = new Map<string, T>();
private expiration = new Map<string, number>();
set(key: string, value: T, ttl: number = 30000): void {
this.data.set(key, value);
this.expiration.set(key, Date.now() + ttl);
// 设置自动过期
setTimeout(() => {
if (this.expiration.get(key)! <= Date.now()) {
this.delete(key);
}
}, ttl);
}
get(key: string): T | undefined {
if (!this.data.has(key)) return undefined;
const expiry = this.expiration.get(key)!;
if (expiry <= Date.now()) {
this.delete(key);
return undefined;
}
return this.data.get(key);
}
delete(key: string): boolean {
this.expiration.delete(key);
return this.data.delete(key);
}
}
// 使用
const userCache = new Cache<User>();
userCache.set('user-1', { id: 1, name: 'Alice' });
const configCache = new Cache<{ theme: string }>();
configCache.set('preferences', { theme: 'dark' });
通用数据包装器
typescript
class Wrapper<T> {
constructor(private value: T) {}
get(): T {
return this.value;
}
set(newValue: T): void {
this.value = newValue;
}
map<U>(fn: (value: T) => U): Wrapper<U> {
return new Wrapper(fn(this.value));
}
}
// 使用
const numberWrapper = new Wrapper(10);
const doubled = numberWrapper.map(n => n * 2); // Wrapper<number>
const stringWrapper = new Wrapper('hello');
const upperCased = stringWrapper.map(s => s.toUpperCase()); // Wrapper<string>
五、泛型约束:精确控制类型范围
基础约束:确保属性存在
typescript
// 确保类型有length属性
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(item: T): void {
console.log(`长度:${item.length}`);
}
logLength('hello'); // 5
logLength([1, 2, 3]); // 3
logLength({ length: 10 }); // 10
// logLength(123); // 错误:数字没有length属性
高级约束:keyof 和对象属性
typescript
// 获取对象属性值
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'Bob', age: 30 };
console.log(getProperty(user, 'name')); // 'Bob'
// getProperty(user, 'email'); // 错误:email不在user的属性中
// 动态创建对象
function createObject<T>(keys: (keyof T)[], values: any[]): T {
return keys.reduce((obj, key, index) => {
obj[key] = values[index];
return obj;
}, {} as T);
}
const newUser = createObject<User>(['id', 'name'], [2, 'Charlie']);
// { id: 2, name: 'Charlie' }
六、实际应用场景与最佳实践
1. API 客户端封装
typescript
class ApiClient {
async get<T>(url: string): Promise<T> {
const response = await fetch(url);
return response.json();
}
async post<T, U>(url: string, body: T): Promise<U> {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
return response.json();
}
}
// 使用
const api = new ApiClient();
const user = await api.get<User>('/api/users/1');
const newProduct = await api.post<Product, ApiResponse<Product>>(
'/api/products',
{ name: 'Tablet', price: 499 }
);
2. 表单验证器
typescript
interface ValidationRule<T> {
value: T;
required?: boolean;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
}
function validate<T>(rule: ValidationRule<T>): string[] {
const errors: string[] = [];
if (rule.required) {
if (!rule.value) errors.push('字段为必填项');
}
if (typeof rule.value === 'string') {
if (rule.minLength && rule.value.length < rule.minLength) {
errors.push(`长度不能少于 ${rule.minLength} 个字符`);
}
if (rule.maxLength && rule.value.length > rule.maxLength) {
errors.push(`长度不能超过 ${rule.maxLength} 个字符`);
}
}
if (typeof rule.value === 'number') {
if (rule.min && rule.value < rule.min) {
errors.push(`值不能小于 ${rule.min}`);
}
if (rule.max && rule.value > rule.max) {
errors.push(`值不能大于 ${rule.max}`);
}
}
return errors;
}
// 使用
const usernameErrors = validate<string>({
value: 'al',
required: true,
minLength: 3
});
// ['长度不能少于 3 个字符']
const ageErrors = validate<number>({
value: 15,
min: 18
});
// ['值不能小于 18']
七、泛型开发最佳实践
-
命名规范
- 使用有意义的类型变量名(不只是 T)
- 推荐:
TData、TResponse、TEntity等
-
优先类型推断typescript
// 好的写法:让TS自动推断 const numbers = createRepeatArray(5, 3); // 不必要的写法:显式指定类型 const numbers = createRepeatArray<number>(5, 3); -
合理使用约束
- 避免过度约束限制灵活性
- 只添加必要的约束条件
-
结合类型别名typescript
// 复杂泛型使用类型别名 type ApiResult<T> = { success: boolean; data: T; error?: string; }; function handleResponse<T>(response: ApiResult<T>) { // ... } -
避免泛型过度使用
- 简单场景不需要泛型
- 优先考虑函数重载
typescript
// 函数重载替代简单泛型 function process(value: string): string; function process(value: number): number; function process(value: any): any { // 实现... }