简体中文 繁體中文 English Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français Japanese

站内搜索

搜索

活动公告

通知:为庆祝网站一周年,将在5.1日与5.2日开放注册,具体信息请见后续详细公告
04-22 00:04
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

深入浅出TypeScript泛型编程从基础语法到高级应用提升代码复用性与类型安全性的实用技巧

SunJu_FaceMall

3万

主题

1158

科技点

3万

积分

白金月票

碾压王

积分
32796

立华奏

发表于 2025-10-2 14:10:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

TypeScript作为JavaScript的超集,为我们带来了静态类型检查的能力,大大提高了代码的可靠性和可维护性。在TypeScript的众多特性中,泛型(Generics)是一个尤为强大的工具,它允许我们在定义函数、类和接口时使用类型变量,从而创建可重用的、灵活且类型安全的代码组件。本文将深入探讨TypeScript泛型编程的各个方面,从基础语法到高级应用,帮助读者全面理解并掌握这一重要特性。

一、泛型基础概念

1. 什么是泛型

泛型是一种允许在定义函数、类或接口时使用类型变量的编程特性。通过泛型,我们可以编写适用于多种类型的代码,同时保持类型安全。简单来说,泛型就像是函数的类型参数,让我们能够创建”类型变量”,这些变量可以代表任何类型,在使用时再指定具体的类型。

2. 为什么需要泛型

在没有泛型的情况下,我们可能会使用any类型来处理多种类型的数据,但这会失去类型检查的优势。例如:
  1. function identity(arg: any): any {
  2.     return arg;
  3. }
复制代码

这个函数可以接受任何类型的参数并返回它,但我们失去了参数和返回值之间的类型关联。使用泛型,我们可以保持这种类型关系:
  1. function identity<T>(arg: T): T {
  2.     return arg;
  3. }
复制代码

这里,T是一个类型变量,它捕获了用户提供的类型,使我们能够在函数中使用它,并保持输入和输出之间的类型一致性。

3. 泛型的基本语法

泛型的基本语法是在函数名、类名或接口名后面使用尖括号<>来定义类型变量:
  1. // 泛型函数
  2. function functionName<T>(param: T): T {
  3.     // 函数体
  4. }
  5. // 泛型接口
  6. interface InterfaceName<T> {
  7.     property: T;
  8.     method(param: T): T;
  9. }
  10. // 泛型类
  11. class ClassName<T> {
  12.     property: T;
  13.     constructor(param: T) {
  14.         this.property = param;
  15.     }
  16.     method(): T {
  17.         return this.property;
  18.     }
  19. }
复制代码

二、泛型函数

1. 基本泛型函数

让我们从最基本的泛型函数开始:
  1. // 泛型函数示例
  2. function identity<T>(arg: T): T {
  3.     return arg;
  4. }
  5. // 使用方式一:明确指定类型
  6. let output1 = identity<string>("hello world");
  7. console.log(output1); // "hello world"
  8. // 使用方式二:类型推断(推荐)
  9. let output2 = identity(42); // TypeScript推断T为number
  10. console.log(output2); // 42
复制代码

在这个例子中,identity函数接受一个类型为T的参数,并返回一个相同类型的值。当我们调用这个函数时,可以明确指定类型,也可以让TypeScript根据传入的参数自动推断类型。

2. 多个类型参数

泛型函数可以有多个类型参数:
  1. function pair<T, U>(first: T, second: U): [T, U] {
  2.     return [first, second];
  3. }
  4. let pairOutput = pair<string, number>("hello", 42);
  5. console.log(pairOutput); // ["hello", 42]
  6. // 使用类型推断
  7. let pairOutput2 = pair(true, "world");
  8. console.log(pairOutput2); // [true, "world"]
复制代码

3. 泛型函数与数组

泛型在处理数组时特别有用:
  1. // 获取数组的第一个元素
  2. function getFirstElement<T>(arr: T[]): T | undefined {
  3.     return arr.length > 0 ? arr[0] : undefined;
  4. }
  5. let numbers = [1, 2, 3, 4, 5];
  6. let firstNumber = getFirstElement(numbers);
  7. console.log(firstNumber); // 1
  8. let strings = ["apple", "banana", "cherry"];
  9. let firstString = getFirstElement(strings);
  10. console.log(firstString); // "apple"
  11. // 空数组情况
  12. let emptyArray: number[] = [];
  13. let emptyResult = getFirstElement(emptyArray);
  14. console.log(emptyResult); // undefined
复制代码

4. 泛型函数与对象

泛型函数也可以处理对象:
  1. function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  2.     return obj[key];
  3. }
  4. interface Person {
  5.     name: string;
  6.     age: number;
  7.     address: string;
  8. }
  9. const person: Person = {
  10.     name: "John Doe",
  11.     age: 30,
  12.     address: "123 Main St"
  13. };
  14. let name = getProperty(person, "name");
  15. console.log(name); // "John Doe"
  16. let age = getProperty(person, "age");
  17. console.log(age); // 30
  18. // 以下代码会报错,因为"phone"不是Person的键
  19. // let phone = getProperty(person, "phone");
复制代码

在这个例子中,我们使用了keyof关键字来约束K类型,确保key参数必须是T类型的键。

三、泛型接口

1. 基本泛型接口

泛型接口允许我们定义具有泛型类型的接口:
  1. interface GenericIdentityFn<T> {
  2.     (arg: T): T;
  3. }
  4. function identity<T>(arg: T): T {
  5.     return arg;
  6. }
  7. let myIdentity: GenericIdentityFn<number> = identity;
  8. console.log(myIdentity(10)); // 10
复制代码

2. 泛型接口与对象

泛型接口可以用于描述对象的形状:
  1. interface Collection<T> {
  2.     add(item: T): void;
  3.     remove(item: T): boolean;
  4.     get(index: number): T | undefined;
  5.     size: number;
  6. }
  7. class ArrayCollection<T> implements Collection<T> {
  8.     private items: T[] = [];
  9.     add(item: T): void {
  10.         this.items.push(item);
  11.     }
  12.     remove(item: T): boolean {
  13.         const index = this.items.indexOf(item);
  14.         if (index !== -1) {
  15.             this.items.splice(index, 1);
  16.             return true;
  17.         }
  18.         return false;
  19.     }
  20.     get(index: number): T | undefined {
  21.         return this.items[index];
  22.     }
  23.     get size(): number {
  24.         return this.items.length;
  25.     }
  26. }
  27. // 使用字符串集合
  28. const stringCollection = new ArrayCollection<string>();
  29. stringCollection.add("hello");
  30. stringCollection.add("world");
  31. console.log(stringCollection.get(0)); // "hello"
  32. console.log(stringCollection.size); // 2
  33. // 使用数字集合
  34. const numberCollection = new ArrayCollection<number>();
  35. numberCollection.add(1);
  36. numberCollection.add(2);
  37. console.log(numberCollection.get(1)); // 2
  38. console.log(numberCollection.size); // 2
复制代码

3. 泛型接口与函数

泛型接口可以用于描述函数类型:
  1. interface SearchFunc<T> {
  2.     (source: T[], searchTerm: T): boolean;
  3. }
  4. let numberSearch: SearchFunc<number> = function(source, searchTerm) {
  5.     return source.indexOf(searchTerm) !== -1;
  6. };
  7. console.log(numberSearch([1, 2, 3, 4, 5], 3)); // true
  8. console.log(numberSearch([1, 2, 3, 4, 5], 6)); // false
  9. let stringSearch: SearchFunc<string> = function(source, searchTerm) {
  10.     return source.includes(searchTerm);
  11. };
  12. console.log(stringSearch(["apple", "banana", "cherry"], "banana")); // true
  13. console.log(stringSearch(["apple", "banana", "cherry"], "grape")); // false
复制代码

四、泛型类

1. 基本泛型类

泛型类与泛型接口类似,使用类型参数来描述类的属性和方法:
  1. class GenericNumber<T> {
  2.     zeroValue: T;
  3.     add: (x: T, y: T) => T;
  4.     constructor(zero: T, addFn: (x: T, y: T) => T) {
  5.         this.zeroValue = zero;
  6.         this.add = addFn;
  7.     }
  8. }
  9. let myGenericNumber = new GenericNumber<number>(0, (x, y) => x + y);
  10. console.log(myGenericNumber.add(5, 10)); // 15
  11. let stringNumeric = new GenericNumber<string>("", (x, y) => x + y);
  12. console.log(stringNumeric.add("hello, ", "world")); // "hello, world"
复制代码

2. 泛型类与继承

泛型类可以继承其他类或实现接口:
  1. interface Printable {
  2.     print(): void;
  3. }
  4. class Box<T> implements Printable {
  5.     private content: T;
  6.     constructor(content: T) {
  7.         this.content = content;
  8.     }
  9.     getContent(): T {
  10.         return this.content;
  11.     }
  12.     setContent(content: T): void {
  13.         this.content = content;
  14.     }
  15.     print(): void {
  16.         console.log(`Box contains: ${this.content}`);
  17.     }
  18. }
  19. class SpecialBox<T> extends Box<T> {
  20.     private specialProperty: string;
  21.     constructor(content: T, specialProperty: string) {
  22.         super(content);
  23.         this.specialProperty = specialProperty;
  24.     }
  25.     getSpecialProperty(): string {
  26.         return this.specialProperty;
  27.     }
  28.     print(): void {
  29.         super.print();
  30.         console.log(`Special property: ${this.specialProperty}`);
  31.     }
  32. }
  33. const numberBox = new Box<number>(42);
  34. numberBox.print(); // "Box contains: 42"
  35. const stringSpecialBox = new SpecialBox<string>("Hello", "This is special");
  36. stringSpecialBox.print();
  37. // "Box contains: Hello"
  38. // "Special property: This is special"
复制代码

3. 泛型类与静态成员

需要注意的是,泛型类型不能用于类的静态成员:
  1. class GenericClass<T> {
  2.     static staticProperty: T; // 错误:静态成员不能引用类类型参数
  3.    
  4.     instanceProperty: T; // 正确:实例成员可以引用类类型参数
  5.    
  6.     constructor(value: T) {
  7.         this.instanceProperty = value;
  8.     }
  9. }
复制代码

五、泛型约束

1. 基本泛型约束

有时我们希望限制泛型类型必须满足某些条件,这时可以使用泛型约束:
  1. interface Lengthwise {
  2.     length: number;
  3. }
  4. function loggingIdentity<T extends Lengthwise>(arg: T): T {
  5.     console.log(`Length: ${arg.length}`);
  6.     return arg;
  7. }
  8. loggingIdentity("hello world"); // 正确:字符串有length属性
  9. loggingIdentity([1, 2, 3]); // 正确:数组有length属性
  10. loggingIdentity({ length: 10, value: 3 }); // 正确:对象有length属性
  11. // loggingIdentity(42); // 错误:数字没有length属性
复制代码

2. 在泛型约束中使用类型参数

我们可以声明一个类型参数,且它被另一个类型参数所约束:
  1. function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  2.     return obj[key];
  3. }
  4. interface Person {
  5.     name: string;
  6.     age: number;
  7. }
  8. const person: Person = {
  9.     name: "Alice",
  10.     age: 30
  11. };
  12. let name = getProperty(person, "name"); // 类型为string
  13. let age = getProperty(person, "age"); // 类型为number
  14. // let phone = getProperty(person, "phone"); // 错误:'phone'不是'Person'的键
复制代码

3. 多重泛型约束

一个类型参数可以有多个约束:
  1. interface Serializable {
  2.     serialize(): string;
  3. }
  4. interface Deserializable<T> {
  5.     deserialize(data: string): T;
  6. }
  7. class DataProcessor<T extends Serializable & Deserializable<T>> {
  8.     process(data: T): string {
  9.         return data.serialize();
  10.     }
  11.    
  12.     restore(dataString: string): T {
  13.         const newData = {} as T;
  14.         return newData.deserialize(dataString);
  15.     }
  16. }
  17. class User implements Serializable, Deserializable<User> {
  18.     constructor(public name: string, public age: number) {}
  19.    
  20.     serialize(): string {
  21.         return JSON.stringify({ name: this.name, age: this.age });
  22.     }
  23.    
  24.     deserialize(data: string): User {
  25.         const obj = JSON.parse(data);
  26.         return new User(obj.name, obj.age);
  27.     }
  28. }
  29. const userProcessor = new DataProcessor<User>();
  30. const user = new User("Bob", 25);
  31. const serialized = userProcessor.process(user);
  32. console.log(serialized); // {"name":"Bob","age":25}
  33. const restoredUser = userProcessor.restore(serialized);
  34. console.log(restoredUser.name); // "Bob"
  35. console.log(restoredUser.age); // 25
复制代码

六、泛型工具类型

TypeScript提供了一些内置的泛型工具类型,可以帮助我们进行常见的类型转换。

1. Partial

Partial<T>将类型T的所有属性变为可选:
  1. interface Todo {
  2.     title: string;
  3.     description: string;
  4.     completed: boolean;
  5. }
  6. function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>): Todo {
  7.     return { ...todo, ...fieldsToUpdate };
  8. }
  9. const todo1: Todo = {
  10.     title: "Learn TypeScript",
  11.     description: "Study generics in TypeScript",
  12.     completed: false
  13. };
  14. const todo2 = updateTodo(todo1, {
  15.     description: "Study advanced generics in TypeScript"
  16. });
  17. console.log(todo2);
  18. // {
  19. //   title: "Learn TypeScript",
  20. //   description: "Study advanced generics in TypeScript",
  21. //   completed: false
  22. // }
复制代码

2. Required

Required<T>将类型T的所有属性变为必选:
  1. interface Props {
  2.     a?: number;
  3.     b?: string;
  4. }
  5. const obj: Props = { a: 5 }; // 正确:a和b都是可选的
  6. const obj2: Required<Props> = { a: 5 }; // 错误:b是必选的
  7. const obj3: Required<Props> = { a: 5, b: "hello" }; // 正确
复制代码

3. Readonly

Readonly<T>将类型T的所有属性变为只读:
  1. interface Todo {
  2.     title: string;
  3. }
  4. const todo: Readonly<Todo> = {
  5.     title: "Delete inactive users"
  6. };
  7. todo.title = "Hello"; // 错误:title是只读的
复制代码

4. Record

Record<K, T>创建一个对象类型,其属性键为K类型,属性值为T类型:
  1. interface PageInfo {
  2.     title: string;
  3. }
  4. type Page = "home" | "about" | "contact";
  5. const pages: Record<Page, PageInfo> = {
  6.     home: { title: "Home Page" },
  7.     about: { title: "About Us" },
  8.     contact: { title: "Contact Us" }
  9. };
  10. console.log(pages.home.title); // "Home Page"
复制代码

5. Pick

Pick<T, K>从类型T中选择一组属性K来创建新类型:
  1. interface Todo {
  2.     title: string;
  3.     description: string;
  4.     completed: boolean;
  5. }
  6. type TodoPreview = Pick<Todo, "title" | "completed">;
  7. const todo: TodoPreview = {
  8.     title: "Clean room",
  9.     completed: false
  10. };
  11. console.log(todo.title); // "Clean room"
  12. console.log(todo.completed); // false
  13. // console.log(todo.description); // 错误:description属性不存在
复制代码

6. Omit

Omit<T, K>从类型T中排除一组属性K来创建新类型:
  1. interface Todo {
  2.     title: string;
  3.     description: string;
  4.     completed: boolean;
  5.     createdAt: number;
  6. }
  7. type TodoPreview = Omit<Todo, "description" | "createdAt">;
  8. const todo: TodoPreview = {
  9.     title: "Clean room",
  10.     completed: false
  11. };
  12. console.log(todo.title); // "Clean room"
  13. console.log(todo.completed); // false
  14. // console.log(todo.description); // 错误:description属性不存在
  15. // console.log(todo.createdAt); // 错误:createdAt属性不存在
复制代码

7. Exclude

Exclude<T, U>从类型T中排除可以赋值给U的类型:
  1. type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
  2. type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
  3. type T2 = Exclude<string | number | (() => void), Function>; // string | number
复制代码

8. Extract

Extract<T, U>从类型T中提取可以赋值给U的类型:
  1. type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
  2. type T1 = Extract<string | number | (() => void), Function>; // () => void
复制代码

9. NonNullable

NonNullable<T>从类型T中排除null和undefined:
  1. type T0 = NonNullable<string | number | undefined>; // string | number
  2. type T1 = NonNullable<string[] | null | undefined>; // string[]
复制代码

10. ReturnType

ReturnType<T>获取函数类型T的返回类型:
  1. type T0 = ReturnType<() => string>; // string
  2. type T1 = ReturnType<(s: string) => void>; // void
  3. type T2 = ReturnType<<T>() => T>; // {}
  4. type T3 = ReturnType<<T extends U, U extends number[]>() => T>; // number[]
  5. type T4 = ReturnType<any>; // any
  6. type T5 = ReturnType<never>; // any
  7. type T6 = ReturnType<string>; // 错误
  8. type T7 = ReturnType<Function>; // any
复制代码

七、泛型的高级应用

1. 条件类型

条件类型允许我们根据条件选择类型,形式为T extends U ? X : Y:
  1. type TypeName<T> =
  2.     T extends string ? "string" :
  3.     T extends number ? "number" :
  4.     T extends boolean ? "boolean" :
  5.     T extends undefined ? "undefined" :
  6.     T extends Function ? "function" :
  7.     "object";
  8. type T0 = TypeName<string>; // "string"
  9. type T1 = TypeName<"hello">; // "string"
  10. type T2 = TypeName<42>; // "number"
  11. type T3 = TypeName<true>; // "boolean"
  12. type T4 = TypeName<undefined>; // "undefined"
  13. type T5 = TypeName<() => void>; // "function"
  14. type T6 = TypeName<{ a: number }>; // "object"
复制代码

2. 映射类型

映射类型允许我们基于旧类型创建新类型:
  1. type OptionsFlags<T> = {
  2.     [Property in keyof T]: boolean;
  3. };
  4. interface Features {
  5.     darkMode: () => void;
  6.     newUserProfile: () => void;
  7. }
  8. type FeatureOptions = OptionsFlags<Features>;
  9. // 等同于:
  10. // {
  11. //     darkMode: boolean;
  12. //     newUserProfile: boolean;
  13. // }
复制代码

3. 映射修饰符

我们可以使用+或-前缀来添加或移除修饰符:
  1. // 移除只读属性
  2. type CreateMutable<T> = {
  3.     -readonly [P in keyof T]: T[P];
  4. };
  5. interface LockedAccount {
  6.     readonly id: string;
  7.     readonly name: string;
  8. }
  9. type UnlockedAccount = CreateMutable<LockedAccount>;
  10. // 等同于:
  11. // {
  12. //     id: string;
  13. //     name: string;
  14. // }
  15. // 添加只读属性
  16. type Locked<T> = {
  17.     +readonly [P in keyof T]: T[P];
  18. };
  19. type LockedAccount2 = Locked<{ id: string; name: string }>;
  20. // 等同于:
  21. // {
  22. //     readonly id: string;
  23. //     readonly name: string;
  24. // }
  25. // 添加可选属性
  26. type Partial<T> = {
  27.     [P in keyof T]?: T[P];
  28. };
  29. // 移除可选属性
  30. type Required<T> = {
  31.     [P in keyof T]-?: T[P];
  32. };
复制代码

4. 模板字面量类型

TypeScript 4.1引入了模板字面量类型,它们可以通过字符串操作创建新的类型:
  1. type EmailLocaleIDs = "welcome_email" | "email_heading";
  2. type FooterLocaleIDs = "footer_title" | "footer_sendoff";
  3. type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
  4. // 等同于:
  5. // "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
复制代码

5. 递归类型

TypeScript支持递归类型,允许类型引用自身:
  1. type JsonValue =
  2.     | string
  3.     | number
  4.     | boolean
  5.     | null
  6.     | JsonObject
  7.     | JsonArray;
  8. interface JsonObject {
  9.     [key: string]: JsonValue;
  10. }
  11. interface JsonArray extends Array<JsonValue> {}
  12. const data: JsonValue = {
  13.     name: "John",
  14.     age: 30,
  15.     isStudent: false,
  16.     address: {
  17.         street: "123 Main St",
  18.         city: "New York"
  19.     },
  20.     hobbies: ["reading", "swimming"]
  21. };
复制代码

6. 高阶函数与泛型

泛型在高阶函数中特别有用:
  1. function map<T, U>(array: T[], fn: (item: T) => U): U[] {
  2.     const result: U[] = [];
  3.     for (const item of array) {
  4.         result.push(fn(item));
  5.     }
  6.     return result;
  7. }
  8. const numbers = [1, 2, 3, 4, 5];
  9. const doubled = map(numbers, n => n * 2);
  10. console.log(doubled); // [2, 4, 6, 8, 10]
  11. const strings = ["hello", "world", "typescript"];
  12. const lengths = map(strings, s => s.length);
  13. console.log(lengths); // [5, 5, 10]
复制代码

7. 泛型与装饰器

泛型可以与装饰器结合使用,提供更灵活的元编程能力:
  1. function logClass<T extends { new(...args: any[]): {} }>(constructor: T) {
  2.     return class extends constructor {
  3.         constructor(...args: any[]) {
  4.             super(...args);
  5.             console.log(`Creating instance of ${constructor.name}`);
  6.         }
  7.     };
  8. }
  9. @logClass
  10. class Person {
  11.     constructor(public name: string) {}
  12. }
  13. const person = new Person("Alice"); // 输出: "Creating instance of Person"
  14. console.log(person.name); // "Alice"
复制代码

八、实际项目中的泛型应用

1. API响应处理

在实际项目中,我们经常需要处理各种API响应,泛型可以帮助我们创建类型安全的响应处理逻辑:
  1. interface ApiResponse<T> {
  2.     data: T;
  3.     status: number;
  4.     message: string;
  5.     timestamp: number;
  6. }
  7. interface User {
  8.     id: number;
  9.     name: string;
  10.     email: string;
  11. }
  12. interface Post {
  13.     id: number;
  14.     title: string;
  15.     content: string;
  16.     authorId: number;
  17. }
  18. async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
  19.     const response = await fetch(url);
  20.     const data = await response.json();
  21.     return {
  22.         data,
  23.         status: response.status,
  24.         message: response.statusText,
  25.         timestamp: Date.now()
  26.     };
  27. }
  28. // 获取用户数据
  29. async function getUser(id: number): Promise<User> {
  30.     const response = await fetchApi<User>(`/api/users/${id}`);
  31.     return response.data;
  32. }
  33. // 获取文章数据
  34. async function getPost(id: number): Promise<Post> {
  35.     const response = await fetchApi<Post>(`/api/posts/${id}`);
  36.     return response.data;
  37. }
  38. // 使用示例
  39. getUser(1).then(user => {
  40.     console.log(user.name); // 类型安全,user被识别为User类型
  41. });
  42. getPost(1).then(post => {
  43.     console.log(post.title); // 类型安全,post被识别为Post类型
  44. });
复制代码

2. 状态管理

在状态管理中,泛型可以帮助我们创建可重用的状态管理逻辑:
  1. interface State<T> {
  2.     data: T | null;
  3.     loading: boolean;
  4.     error: Error | null;
  5. }
  6. class Store<T> {
  7.     private state: State<T> = {
  8.         data: null,
  9.         loading: false,
  10.         error: null
  11.     };
  12.     private listeners: Array<(state: State<T>) => void> = [];
  13.     getState(): State<T> {
  14.         return { ...this.state };
  15.     }
  16.     subscribe(listener: (state: State<T>) => void): () => void {
  17.         this.listeners.push(listener);
  18.         return () => {
  19.             this.listeners = this.listeners.filter(l => l !== listener);
  20.         };
  21.     }
  22.     private notify() {
  23.         this.listeners.forEach(listener => listener(this.getState()));
  24.     }
  25.     async load(loader: () => Promise<T>): Promise<void> {
  26.         this.state = {
  27.             data: null,
  28.             loading: true,
  29.             error: null
  30.         };
  31.         this.notify();
  32.         try {
  33.             const data = await loader();
  34.             this.state = {
  35.                 data,
  36.                 loading: false,
  37.                 error: null
  38.             };
  39.         } catch (error) {
  40.             this.state = {
  41.                 data: null,
  42.                 loading: false,
  43.                 error: error instanceof Error ? error : new Error(String(error))
  44.             };
  45.         }
  46.         this.notify();
  47.     }
  48. }
  49. // 使用示例
  50. interface User {
  51.     id: number;
  52.     name: string;
  53. }
  54. const userStore = new Store<User>();
  55. userStore.subscribe(state => {
  56.     if (state.loading) {
  57.         console.log("Loading user data...");
  58.     } else if (state.error) {
  59.         console.error("Error loading user data:", state.error.message);
  60.     } else if (state.data) {
  61.         console.log("User data loaded:", state.data.name);
  62.     }
  63. });
  64. // 模拟API调用
  65. async function fetchUser(): Promise<User> {
  66.     return new Promise(resolve => {
  67.         setTimeout(() => {
  68.             resolve({ id: 1, name: "John Doe" });
  69.         }, 1000);
  70.     });
  71. }
  72. userStore.load(fetchUser);
复制代码

3. 表单处理

在表单处理中,泛型可以帮助我们创建类型安全的表单处理逻辑:
  1. interface FormField<T> {
  2.     value: T;
  3.     error: string | null;
  4.     touched: boolean;
  5.     validate: (value: T) => string | null;
  6. }
  7. interface Form<T> {
  8.     fields: { [K in keyof T]: FormField<T[K]> };
  9.     isValid: boolean;
  10. }
  11. function createForm<T>(initialValues: T, validators: { [K in keyof T]?: (value: T[K]) => string | null }): Form<T> {
  12.     const fields: { [K in keyof T]: FormField<T[K]> } = {} as any;
  13.    
  14.     for (const key in initialValues) {
  15.         const value = initialValues[key];
  16.         const validate = validators[key] || (() => null);
  17.         
  18.         fields[key] = {
  19.             value,
  20.             error: null,
  21.             touched: false,
  22.             validate
  23.         };
  24.     }
  25.    
  26.     return {
  27.         fields,
  28.         isValid: true
  29.     };
  30. }
  31. function updateField<T, K extends keyof T>(form: Form<T>, field: K, value: T[K]): Form<T> {
  32.     const updatedFields = { ...form.fields };
  33.     const fieldDef = updatedFields[field];
  34.    
  35.     const error = fieldDef.validate(value);
  36.    
  37.     updatedFields[field] = {
  38.         ...fieldDef,
  39.         value,
  40.         error,
  41.         touched: true
  42.     };
  43.    
  44.     const isValid = Object.values(updatedFields).every(f => !f.error);
  45.    
  46.     return {
  47.         fields: updatedFields,
  48.         isValid
  49.     };
  50. }
  51. // 使用示例
  52. interface LoginForm {
  53.     email: string;
  54.     password: string;
  55. }
  56. const loginForm = createForm<LoginForm>(
  57.     { email: "", password: "" },
  58.     {
  59.         email: (value) => {
  60.             if (!value) return "Email is required";
  61.             if (!/\S+@\S+\.\S+/.test(value)) return "Email is invalid";
  62.             return null;
  63.         },
  64.         password: (value) => {
  65.             if (!value) return "Password is required";
  66.             if (value.length < 8) return "Password must be at least 8 characters";
  67.             return null;
  68.         }
  69.     }
  70. );
  71. // 更新表单字段
  72. let updatedForm = updateField(loginForm, "email", "test@example.com");
  73. updatedForm = updateField(updatedForm, "password", "password123");
  74. console.log(updatedForm.isValid); // true
  75. console.log(updatedForm.fields.email.error); // null
  76. console.log(updatedForm.fields.password.error); // null
复制代码

4. 组件库开发

在开发React组件库时,泛型可以帮助我们创建灵活且类型安全的组件:
  1. import React from 'react';
  2. interface ListProps<T> {
  3.     items: T[];
  4.     renderItem: (item: T) => React.ReactNode;
  5.     keyExtractor: (item: T) => string | number;
  6.     className?: string;
  7. }
  8. function List<T>({ items, renderItem, keyExtractor, className }: ListProps<T>): React.ReactElement {
  9.     return (
  10.         <ul className={className}>
  11.             {items.map(item => (
  12.                 <li key={keyExtractor(item)}>
  13.                     {renderItem(item)}
  14.                 </li>
  15.             ))}
  16.         </ul>
  17.     );
  18. }
  19. interface User {
  20.     id: number;
  21.     name: string;
  22.     email: string;
  23. }
  24. interface Product {
  25.     id: string;
  26.     name: string;
  27.     price: number;
  28. }
  29. // 使用示例
  30. const UserList: React.FC<{ users: User[] }> = ({ users }) => (
  31.     <List
  32.         items={users}
  33.         renderItem={user => (
  34.             <div>
  35.                 <h3>{user.name}</h3>
  36.                 <p>{user.email}</p>
  37.             </div>
  38.         )}
  39.         keyExtractor={user => user.id}
  40.         className="user-list"
  41.     />
  42. );
  43. const ProductList: React.FC<{ products: Product[] }> = ({ products }) => (
  44.     <List
  45.         items={products}
  46.         renderItem={product => (
  47.             <div>
  48.                 <h3>{product.name}</h3>
  49.                 <p>${product.price.toFixed(2)}</p>
  50.             </div>
  51.         )}
  52.         keyExtractor={product => product.id}
  53.         className="product-list"
  54.     />
  55. );
复制代码

九、泛型编程的最佳实践

1. 使用有意义的类型参数名称

虽然使用单个大写字母(如T、U、K等)作为类型参数名称是常见的做法,但在某些情况下,使用更具描述性的名称可以提高代码的可读性:
  1. // 不够清晰
  2. function get<T>(array: T[], index: number): T | undefined {
  3.     return array[index];
  4. }
  5. // 更清晰
  6. function getElement<Element>(array: Element[], index: number): Element | undefined {
  7.     return array[index];
  8. }
复制代码

2. 限制泛型的范围

尽可能使用泛型约束来限制泛型的范围,这样可以提供更好的类型安全性和代码提示:
  1. // 不够安全
  2. function logLength<T>(arg: T): void {
  3.     // console.log(arg.length); // 错误:T不一定有length属性
  4. }
  5. // 更安全
  6. function logLength<T extends { length: number }>(arg: T): void {
  7.     console.log(arg.length); // 正确:T有length属性
  8. }
复制代码

3. 避免过度使用泛型

虽然泛型很强大,但并不是所有情况都需要使用泛型。过度使用泛型会使代码变得复杂且难以理解:
  1. // 过度使用泛型
  2. interface Pair<T, U> {
  3.     first: T;
  4.     second: U;
  5. }
  6. function createPair<T, U>(first: T, second: U): Pair<T, U> {
  7.     return { first, second };
  8. }
  9. // 更简单且足够
  10. interface StringNumberPair {
  11.     first: string;
  12.     second: number;
  13. }
  14. function createStringNumberPair(first: string, second: number): StringNumberPair {
  15.     return { first, second };
  16. }
复制代码

4. 提供默认类型参数

在适当的情况下,为泛型参数提供默认类型可以提高代码的灵活性:
  1. interface ApiResponse<Data = any> {
  2.     data: Data;
  3.     status: number;
  4.     message: string;
  5. }
  6. // 使用默认类型
  7. const response: ApiResponse = {
  8.     data: { name: "John" },
  9.     status: 200,
  10.     message: "OK"
  11. };
  12. // 指定类型
  13. const userResponse: ApiResponse<User> = {
  14.     data: { id: 1, name: "John", email: "john@example.com" },
  15.     status: 200,
  16.     message: "OK"
  17. };
复制代码

5. 使用泛型来创建可重用的工具函数

泛型非常适合创建可重用的工具函数:
  1. // 通用数组去重函数
  2. function unique<T>(array: T[], keyFn?: (item: T) => any): T[] {
  3.     const seen = new Set();
  4.     return array.filter(item => {
  5.         const key = keyFn ? keyFn(item) : item;
  6.         if (seen.has(key)) {
  7.             return false;
  8.         }
  9.         seen.add(key);
  10.         return true;
  11.     });
  12. }
  13. // 使用示例
  14. const numbers = [1, 2, 3, 2, 1, 4];
  15. const uniqueNumbers = unique(numbers);
  16. console.log(uniqueNumbers); // [1, 2, 3, 4]
  17. const users = [
  18.     { id: 1, name: "John" },
  19.     { id: 2, name: "Jane" },
  20.     { id: 1, name: "John Doe" }
  21. ];
  22. const uniqueUsers = unique(users, user => user.id);
  23. console.log(uniqueUsers);
  24. // [
  25. //     { id: 1, name: "John" },
  26. //     { id: 2, name: "Jane" }
  27. // ]
复制代码

6. 在文档中说明泛型的用途

当创建复杂的泛型类型或函数时,提供清晰的文档说明泛型的用途和约束:
  1. /**
  2. * 过滤数组中的元素,保留满足条件的元素
  3. * @param array 要过滤的数组
  4. * @param predicate 判断元素是否应该保留的函数
  5. * @returns 过滤后的新数组
  6. * @template T 数组元素的类型
  7. */
  8. function filter<T>(array: T[], predicate: (item: T) => boolean): T[] {
  9.     return array.filter(predicate);
  10. }
复制代码

十、总结

TypeScript的泛型是一个强大的特性,它允许我们编写灵活、可重用且类型安全的代码。通过本文的学习,我们了解了泛型的基本概念、语法和用法,以及如何在函数、接口和类中使用泛型。我们还探讨了泛型约束、泛型工具类型以及泛型的高级应用,如条件类型、映射类型和模板字面量类型。

在实际项目中,泛型可以帮助我们处理API响应、状态管理、表单处理和组件开发等各种场景。通过遵循最佳实践,我们可以充分利用泛型的优势,编写出更加健壮、可维护的代码。

掌握TypeScript泛型编程需要时间和实践,但一旦理解了其核心概念和用法,你将能够编写出更加灵活、类型安全的代码,提高开发效率和代码质量。希望本文能够帮助你深入理解TypeScript泛型编程,并在实际项目中应用这些知识。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

手机版|联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.

>