1. 泛型基础概念
定义:泛型是 TypeScript 中允许创建可复用组件的特性,这些组件可以支持多种数据类型,而非单一特定类型。
核心优势:
- 代码复用性:同一组件可处理不同类型数据
- 类型安全:在编译阶段捕获类型错误
- 灵活性:保持代码的灵活性同时提供强类型支持
泛型函数
// 基础泛型函数语法
function identity<T>(arg: T): T {
return arg;
}
// 使用方式
const result = identity<string>("hello"); // 指定类型参数
const inferredResult = identity(123); // 类型自动推断为 number
泛型类
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
// 使用示例
const numberBox = new Box<number>(42);
const stringBox = new Box("text"); // 类型自动推断
2. 泛型约束详解
基本约束
interface HasLength {
length: number;
}
// 约束 T 必须实现 HasLength 接口
function getLength<T extends HasLength>(arg: T): number {
return arg.length;
}
// 有效调用
getLength("hello"); // 字符串有 length 属性
getLength([1, 2, 3]); // 数组有 length 属性
// 错误调用
getLength(123); // 数字没有 length 属性
键约束
// 约束 K 必须是 T 类型的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // 正确
const invalid = getProperty(user, "address"); // 错误:address 不是 user 的键
构造函数约束
interface Constructor<T> {
new (...args: any[]): T;
}
function createInstance<T>(ctor: Constructor<T>): T {
return new ctor();
}
class MyClass {
constructor() {}
}
const instance = createInstance(MyClass); // 创建 MyClass 实例
3. 接口深入理解
接口定义
interface Person {
name: string;
age?: number; // 可选属性
readonly id: number; // 只读属性
[propName: string]: any; // 任意属性
}
// 使用接口
const person: Person = {
name: "Bob",
id: 12345,
address: "123 Street" // 任意属性
};
person.age = 30; // 可选属性可赋值
person.id = 67890; // 错误:只读属性不可修改
接口继承
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
// 实现接口
const square: Square = {
color: "blue",
sideLength: 10
};
函数类型接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
const mySearch: SearchFunc = function(src, sub) {
return src.includes(sub);
};
4. 泛型与接口结合应用
泛型接口
interface Container<T> {
value: T;
getValue: () => T;
setValue: (newValue: T) => void;
}
// 实现泛型接口
class BoxContainer<T> implements Container<T> {
constructor(private _value: T) {}
getValue() {
return this._value;
}
setValue(newValue: T) {
this._value = newValue;
}
}
泛型工具类型
// Partial<T> - 创建所有属性可选的类型
interface User {
name: string;
age: number;
}
type PartialUser = Partial<User>;
// 等价于 { name?: string; age?: number; }
// Readonly<T> - 创建所有属性只读的类型
type ReadonlyUser = Readonly<User>;
// Pick<T, K> - 从 T 中选择一组属性 K
type NameOnly = Pick<User, "name">;
// 等价于 { name: string; }
// Record<K, T> - 创建一个 K 类型为键,T 类型为值的对象类型
type UserRecords = Record<string, User>;
5. 高级泛型技术
条件类型
// 基本条件类型语法
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
type StringArray = ToArray<string>; // string[]
type NumberOrStringArray = ToArray<number | string>; // number[] | string[]
映射类型
// 基本映射类型
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Optional<T> = {
[P in keyof T]?: T[P];
};
// 使用示例
type ReadonlyUser = Readonly<User>;
type OptionalUser = Optional<User>;
类型推断与 infer
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
type Num = ReturnType<() => number>; // number
type Str = ReturnType<(s: string) => string>; // string
案例 1:实现一个通用的响应接口
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 使用示例
type UserResponse = ApiResponse<User>;
type ProductListResponse = ApiResponse<Product[]>;
// 处理 API 响应
function processResponse<T>(response: ApiResponse<T>): T {
if (response.code === 200) {
return response.data;
}
throw new Error(response.message);
}
案例 2:实现一个泛型缓存类
class Cache<T> {
private storage: Map<string, T> = new Map();
set(key: string, value: T): void {
this.storage.set(key, value);
}
get(key: string): T | undefined {
return this.storage.get(key);
}
clear(): void {
this.storage.clear();
}
}
// 使用示例
const userCache = new Cache<User>();
userCache.set("currentUser", { name: "Alice", age: 30 });
const currentUser = userCache.get("currentUser");
案例总结
- 明确类型参数:除非类型可以被可靠推断,否则明确指定泛型类型参数
- 约束类型参数:使用泛型约束确保类型参数满足特定条件
- 避免过度泛型:仅在必要时使用泛型,避免增加不必要的复杂性
- 优先使用接口而非类型别名:对于对象类型定义,优先使用
interface
而非type
- 使用工具类型:利用 TypeScript 内置的工具类型(Partial、Readonly 等)简化常见操作
常见错误与解决方案
错误 1:泛型函数返回类型不匹配
// 错误示例
function createArray<T>(length: number, value: T): number[] {
return Array(length).fill(value); // 错误:可能返回非 number[] 类型
}
// 解决方案
function createArray<T>(length: number, value: T): T[] {
return Array(length).fill(value);
}
错误 2:接口属性冲突
// 错误示例
interface A { x: number; }
interface B { x: string; }
interface C extends A, B {} // 错误:属性 x 类型冲突
// 解决方案
interface C { x: number | string; } // 联合类型解决冲突
错误 3:泛型约束不足
// 错误示例
function getLength<T>(arg: T): number {
return arg.length; // 错误:T 不一定有 length 属性
}
// 解决方案
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
与其他 TypeScript 特性的组合
泛型与装饰器
function logClass<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
constructor(...args: any[]) {
console.log("Creating instance with arguments:", args);
super(...args);
}
};
}
@logClass
class MyClass {
constructor(public name: string) {}
}
const instance = new MyClass("Test"); // 输出: Creating instance with arguments: ["Test"]
泛型与命名空间
namespace Utils {
export function identity<T>(arg: T): T {
return arg;
}
}
const result = Utils.identity("hello"); // 使用命名空间中的泛型函数
总结
泛型和接口是 TypeScript 中最强大的两个特性,它们共同构成了 TypeScript 类型系统的核心。通过泛型,我们可以创建高度可复用的组件,同时保持类型安全;通过接口,我们可以定义清晰的契约,规范代码结构。合理运用这两个特性,可以显著提升代码质量、可维护性和开发效率。
在实际开发中,建议遵循最佳实践,从简单的泛型应用开始,逐步深入到高级技术。同时,注意避免常见错误,充分利用 TypeScript 提供的工具类型和内置功能,打造健壮、灵活的 TypeScript 应用。