Skip to content

TypeScript 泛型

在 TypeScript 中,泛型(Generics)是一种强大的特性,它允许我们编写可重用的代码,可以适用于多种类型,而不是单一类型。泛型可以提高代码的灵活性和可重用性,同时保持类型安全。本文将详细介绍 TypeScript 中的泛型。

1. 泛型的基本概念

什么是泛型?

泛型是一种参数化类型的机制,它允许我们在定义函数、类、接口时使用类型参数,而不是具体的类型。这样,我们可以编写适用于多种类型的代码,而不需要为每种类型编写重复的代码。

为什么使用泛型?

  • 代码重用:泛型允许我们编写可重用的代码,可以适用于多种类型。
  • 类型安全:泛型在编译时进行类型检查,确保类型安全。
  • 灵活性:泛型使代码更加灵活,可以处理不同类型的数据。

2. 泛型函数

基本用法

使用尖括号(<>)定义泛型参数,然后在函数参数和返回值中使用这些参数。

typescript
// 泛型函数
function identity<T>(value: T): T {
  return value;
}

// 使用泛型函数
const number = identity<number>(42); // 类型为 number
const string = identity<string>("Hello"); // 类型为 string
const boolean = identity<boolean>(true); // 类型为 boolean

// 类型推断
const number2 = identity(42); // 类型为 number
const string2 = identity("Hello"); // 类型为 string

多个泛型参数

函数可以有多个泛型参数。

typescript
// 多个泛型参数
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// 使用
const pair1 = pair<number, string>(1, "one"); // 类型为 [number, string]
const pair2 = pair<string, boolean>("hello", true); // 类型为 [string, boolean]

// 类型推断
const pair3 = pair(2, "two"); // 类型为 [number, string]

泛型约束

使用 extends 关键字对泛型参数进行约束。

typescript
// 泛型约束
interface Lengthwise {
  length: number;
}

function logLength<T extends Lengthwise>(value: T): void {
  console.log(`Length: ${value.length}`);
}

// 正确:string 有 length 属性
logLength("Hello"); // 输出:Length: 5

// 正确:array 有 length 属性
logLength([1, 2, 3]); // 输出:Length: 3

// 错误:number 没有 length 属性
// logLength(42); // 类型 'number' 不能赋值给类型 'Lengthwise'

泛型参数默认类型

TypeScript 2.3+ 支持泛型参数默认类型。

typescript
// 泛型参数默认类型
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

// 使用默认类型
const strings = createArray(3, "hello"); // 类型为 string[]

// 指定类型
const numbers = createArray<number>(3, 42); // 类型为 number[]
const booleans = createArray<boolean>(3, true); // 类型为 boolean[]

3. 泛型类

基本用法

使用尖括号(<>)定义泛型参数,然后在类的属性、方法中使用这些参数。

typescript
// 泛型类
class Container<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  getValue(): T {
    return this.value;
  }

  setValue(value: T): void {
    this.value = value;
  }
}

// 使用泛型类
const numberContainer = new Container<number>(42);
console.log(numberContainer.getValue()); // 输出:42
numberContainer.setValue(100);
console.log(numberContainer.getValue()); // 输出:100

const stringContainer = new Container<string>("Hello");
console.log(stringContainer.getValue()); // 输出:Hello
stringContainer.setValue("World");
console.log(stringContainer.getValue()); // 输出:World

多个泛型参数

类可以有多个泛型参数。

typescript
// 多个泛型参数
class Pair<T, U> {
  private first: T;
  private second: U;

  constructor(first: T, second: U) {
    this.first = first;
    this.second = second;
  }

  getFirst(): T {
    return this.first;
  }

  getSecond(): U {
    return this.second;
  }

  setFirst(value: T): void {
    this.first = value;
  }

  setSecond(value: U): void {
    this.second = value;
  }
}

// 使用
const pair = new Pair<number, string>(1, "one");
console.log(pair.getFirst()); // 输出:1
console.log(pair.getSecond()); // 输出:one

泛型约束

类的泛型参数也可以使用约束。

typescript
// 泛型约束
interface HasId {
  id: number;
}

class Repository<T extends HasId> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  findById(id: number): T | undefined {
    return this.items.find(item => item.id === id);
  }

  getAll(): T[] {
    return this.items;
  }
}

// 定义符合约束的类型
interface User extends HasId {
  name: string;
  email: string;
}

// 使用
const userRepository = new Repository<User>();
userRepository.add({ id: 1, name: "John", email: "john@example.com" });
userRepository.add({ id: 2, name: "Jane", email: "jane@example.com" });

const user = userRepository.findById(1);
console.log(user); // 输出:{ id: 1, name: 'John', email: 'john@example.com' }

const allUsers = userRepository.getAll();
console.log(allUsers); // 输出:所有用户

泛型参数默认类型

类的泛型参数也可以有默认类型。

typescript
// 泛型参数默认类型
class Storage<T = string> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  get(index: number): T | undefined {
    return this.items[index];
  }

  getAll(): T[] {
    return this.items;
  }
}

// 使用默认类型
const stringStorage = new Storage();
stringStorage.add("hello");
stringStorage.add("world");
console.log(stringStorage.getAll()); // 输出:['hello', 'world']

// 指定类型
const numberStorage = new Storage<number>();
numberStorage.add(1);
numberStorage.add(2);
console.log(numberStorage.getAll()); // 输出:[1, 2]

4. 泛型接口

基本用法

使用尖括号(<>)定义泛型参数,然后在接口中使用这些参数。

typescript
// 泛型接口
interface Result<T> {
  success: boolean;
  data: T;
  message?: string;
}

// 使用泛型接口
const successResult: Result<number> = {
  success: true,
  data: 42
};

const errorResult: Result<string> = {
  success: false,
  data: "",
  message: "Error occurred"
};

// 泛型函数使用泛型接口
function createResult<T>(success: boolean, data: T, message?: string): Result<T> {
  return {
    success,
    data,
    message
  };
}

const result1 = createResult<number>(true, 100);
const result2 = createResult<string>(false, "", "Failed");

泛型接口作为函数类型

泛型接口可以作为函数类型。

typescript
// 泛型接口作为函数类型
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);

console.log(numberComparator(5, 3)); // 输出:2
console.log(stringComparator("apple", "banana")); // 输出:-1

泛型接口继承

泛型接口可以继承其他泛型接口。

typescript
// 泛型接口继承
interface Collection<T> {
  add(item: T): void;
  remove(item: T): void;
  size(): number;
}

interface List<T> extends Collection<T> {
  get(index: number): T;
  set(index: number, item: T): void;
}

// 实现泛型接口
class ArrayList<T> implements List<T> {
  private items: T[] = [];

  add(item: T): void {
    this.items.push(item);
  }

  remove(item: T): void {
    const index = this.items.indexOf(item);
    if (index !== -1) {
      this.items.splice(index, 1);
    }
  }

  size(): number {
    return this.items.length;
  }

  get(index: number): T {
    return this.items[index];
  }

  set(index: number, item: T): void {
    this.items[index] = item;
  }
}

// 使用
const list = new ArrayList<number>();
list.add(1);
list.add(2);
list.add(3);
console.log(list.size()); // 输出:3
console.log(list.get(0)); // 输出:1
list.set(0, 10);
console.log(list.get(0)); // 输出:10
list.remove(2);
console.log(list.size()); // 输出:2

5. 泛型约束

基本约束

使用 extends 关键字对泛型参数进行约束。

typescript
// 基本约束
interface HasName {
  name: string;
}

function printName<T extends HasName>(obj: T): void {
  console.log(obj.name);
}

// 正确:符合约束
printName({ name: "John", age: 30 }); // 输出:John

// 错误:不符合约束
// printName({ age: 30 }); // 类型 '{ age: number; }' 不能赋值给类型 'HasName'

多重约束

泛型参数可以有多个约束,使用 & 符号。

typescript
// 多重约束
interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

function printInfo<T extends HasName & HasAge>(obj: T): void {
  console.log(`Name: ${obj.name}, Age: ${obj.age}`);
}

// 正确:符合所有约束
printName({ name: "John", age: 30 }); // 输出:Name: John, Age: 30

// 错误:缺少 age 属性
// printName({ name: "John" }); // 类型 '{ name: string; }' 不能赋值给类型 'HasName & HasAge'

// 错误:缺少 name 属性
// printName({ age: 30 }); // 类型 '{ age: number; }' 不能赋值给类型 'HasName & HasAge'

约束为类

泛型参数可以约束为某个类。

typescript
// 约束为类
class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  makeSound(): void {
    console.log("Some sound");
  }
}

class Dog extends Animal {
  breed: string;
  constructor(name: string, breed: string) {
    super(name);
    this.breed = breed;
  }
  makeSound(): void {
    console.log("Woof! Woof!");
  }
}

class Cat extends Animal {
  constructor(name: string) {
    super(name);
  }
  makeSound(): void {
    console.log("Meow! Meow!");
  }
}

function animalSound<T extends Animal>(animal: T): void {
  console.log(`${animal.name} says:`);
  animal.makeSound();
}

// 正确:Dog 是 Animal 的子类
animalSound(new Dog("Rex", "Labrador")); // 输出:Rex says: Woof! Woof!

// 正确:Cat 是 Animal 的子类
animalSound(new Cat("Whiskers")); // 输出:Whiskers says: Meow! Meow!

// 错误:不是 Animal 的子类
// animalSound({ name: "John" }); // 类型 '{ name: string; }' 不能赋值给类型 'Animal'

6. 泛型工具类型

TypeScript 提供了一些内置的泛型工具类型,用于常见的类型转换操作。

Partial<T>

将类型 T 的所有属性变为可选。

typescript
// Partial<T>
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// 所有属性变为可选
const partialUser: Partial<User> = {
  name: "John",
  email: "john@example.com"
};

Required<T>

将类型 T 的所有属性变为必选。

typescript
// Required<T>
interface User {
  id: number;
  name: string;
  email?: string;
  age?: number;
}

// 所有属性变为必选
const requiredUser: Required<User> = {
  id: 1,
  name: "John",
  email: "john@example.com",
  age: 30
};

Readonly<T>

将类型 T 的所有属性变为只读。

typescript
// Readonly<T>
interface User {
  id: number;
  name: string;
  email: string;
}

// 所有属性变为只读
const readonlyUser: Readonly<User> = {
  id: 1,
  name: "John",
  email: "john@example.com"
};

// 错误:不能修改只读属性
// readonlyUser.name = "Jane"; // 无法分配到 "name" ,因为它是只读属性

Record<K, T>

创建一个类型,其中键的类型为 K,值的类型为 T。

typescript
// Record<K, T>
const users: Record<number, { name: string; email: string }> = {
  1: { name: "John", email: "john@example.com" },
  2: { name: "Jane", email: "jane@example.com" },
  3: { name: "Bob", email: "bob@example.com" }
};

// 使用字符串作为键
const userRoles: Record<string, string> = {
  "john": "admin",
  "jane": "user",
  "bob": "user"
};

Pick<T, K>

从类型 T 中选取属性 K。

typescript
// Pick<T, K>
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  role: string;
}

// 只选取 name 和 email 属性
const userInfo: Pick<User, "name" | "email"> = {
  name: "John",
  email: "john@example.com"
};

Omit<T, K>

从类型 T 中排除属性 K。

typescript
// Omit<T, K>
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  role: string;
}

// 排除 age 和 role 属性
const userInfo: Omit<User, "age" | "role"> = {
  id: 1,
  name: "John",
  email: "john@example.com"
};

Exclude<T, U>

从类型 T 中排除可以赋值给类型 U 的类型。

typescript
// Exclude<T, U>
type T = string | number | boolean;
type U = string | boolean;

type Excluded = Exclude<T, U>; // 类型为 number

// 使用
const value: Excluded = 42; // 正确
// const value: Excluded = "hello"; // 错误:类型 'string' 不能赋值给类型 'number'

Extract<T, U>

从类型 T 中提取可以赋值给类型 U 的类型。

typescript
// Extract<T, U>
type T = string | number | boolean;
type U = string | boolean;

type Extracted = Extract<T, U>; // 类型为 string | boolean

// 使用
const value1: Extracted = "hello"; // 正确
const value2: Extracted = true; // 正确
// const value3: Extracted = 42; // 错误:类型 'number' 不能赋值给类型 'string | boolean'

NonNullable<T>

从类型 T 中排除 null 和 undefined。

typescript
// NonNullable<T>
type T = string | number | null | undefined;
type NonNull = NonNullable<T>; // 类型为 string | number

// 使用
const value1: NonNull = "hello"; // 正确
const value2: NonNull = 42; // 正确
// const value3: NonNull = null; // 错误:类型 'null' 不能赋值给类型 'string | number'
// const value4: NonNull = undefined; // 错误:类型 'undefined' 不能赋值给类型 'string | number'

Parameters<T>

从函数类型 T 中提取参数类型,返回一个元组类型。

typescript
// Parameters<T>
type Func = (a: number, b: string) => boolean;
type FuncParams = Parameters<Func>; // 类型为 [number, string]

// 使用
const params: FuncParams = [42, "hello"];

ReturnType<T>

从函数类型 T 中提取返回类型。

typescript
// ReturnType<T>
type Func = (a: number, b: string) => boolean;
type FuncReturn = ReturnType<Func>; // 类型为 boolean

// 使用
const result: FuncReturn = true;

7. 泛型的高级用法

递归泛型

泛型可以递归使用,用于处理嵌套结构。

typescript
// 递归泛型
interface TreeNode<T> {
  value: T;
  children?: TreeNode<T>[];
}

// 使用
const tree: TreeNode<number> = {
  value: 1,
  children: [
    {
      value: 2,
      children: [
        { value: 3 },
        { value: 4 }
      ]
    },
    {
      value: 5,
      children: [
        { value: 6 }
      ]
    }
  ]
};

// 遍历树
function traverse<T>(node: TreeNode<T>): void {
  console.log(node.value);
  if (node.children) {
    node.children.forEach(child => traverse(child));
  }
}

traverse(tree); // 输出:1 2 3 4 5 6

条件类型

条件类型允许我们根据类型条件选择不同的类型。

typescript
// 条件类型
type IsString<T> = T extends string ? true : false;

// 使用
const isString1: IsString<string> = true; // 正确
const isString2: IsString<number> = false; // 正确

// 更复杂的条件类型
type ExtractArrayType<T> = T extends Array<infer U> ? U : T;

// 使用
type A = ExtractArrayType<string[]>; // 类型为 string
type B = ExtractArrayType<number>; // 类型为 number

映射类型

映射类型允许我们基于现有类型创建新类型。

typescript
// 映射类型
interface User {
  id: number;
  name: string;
  email: string;
}

// 映射为可选属性
type OptionalUser = {
  [K in keyof User]?: User[K];
};

// 映射为只读属性
type ReadonlyUser = {
  readonly [K in keyof User]: User[K];
};

// 使用
const optionalUser: OptionalUser = {
  name: "John"
};

const readonlyUser: ReadonlyUser = {
  id: 1,
  name: "John",
  email: "john@example.com"
};

// 错误:不能修改只读属性
// readonlyUser.name = "Jane"; // 无法分配到 "name" ,因为它是只读属性

8. 泛型的最佳实践

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

使用有意义的泛型参数名,提高代码的可读性。

typescript
// 推荐:使用有意义的泛型参数名
function processArray<T>(array: T[]): T[] {
  // 实现
}

// 不推荐:使用单个字母作为泛型参数名
function processArray<T>(array: T[]): T[] {
  // 实现
}

// 更推荐:使用更具体的泛型参数名
function processUserArray<User>(array: User[]): User[] {
  // 实现
}

2. 使用泛型约束

使用泛型约束来限制泛型参数的类型,提高类型安全性。

typescript
// 推荐:使用泛型约束
interface HasId {
  id: number;
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
  return items.find(item => item.id === id);
}

// 不推荐:不使用泛型约束
function findById<T>(items: T[], id: number): T | undefined {
  // 可能会导致运行时错误
  return items.find((item: any) => item.id === id);
}

3. 使用泛型默认类型

对于常用的泛型参数,使用默认类型可以简化代码。

typescript
// 推荐:使用泛型默认类型
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

// 不推荐:不使用泛型默认类型
function createArray<T>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

4. 合理使用泛型工具类型

使用 TypeScript 提供的泛型工具类型来简化类型定义。

typescript
// 推荐:使用泛型工具类型
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// 使用 Pick 提取需要的属性
type UserInfo = Pick<User, "name" | "email">;

// 不推荐:手动定义
interface UserInfo {
  name: string;
  email: string;
}

5. 避免过度使用泛型

虽然泛型很强大,但过度使用会使代码变得复杂。只在需要时使用泛型。

typescript
// 推荐:只在需要时使用泛型
function identity<T>(value: T): T {
  return value;
}

// 不推荐:过度使用泛型
function add<T>(a: T, b: T): T {
  // 这种情况下,泛型可能不是最佳选择,因为不同类型的加法行为不同
  return a + b; // 可能会导致类型错误
}

9. 常见错误

1. 泛型参数未使用

泛型参数必须在函数或类中使用,否则会导致编译错误。

typescript
// 错误:泛型参数 T 未使用
// function foo<T>(): void {
//   console.log("Hello");
// }

// 正确:使用泛型参数
function foo<T>(value: T): T {
  return value;
}

2. 泛型约束不满足

当使用泛型约束时,传入的类型必须满足约束条件。

typescript
interface HasName {
  name: string;
}

function printName<T extends HasName>(obj: T): void {
  console.log(obj.name);
}

// 错误:传入的类型不满足约束
// printName({ age: 30 }); // 类型 '{ age: number; }' 不能赋值给类型 'HasName'

// 正确:传入的类型满足约束
printName({ name: "John", age: 30 });

3. 泛型类型推断错误

当 TypeScript 无法推断泛型类型时,需要显式指定类型。

typescript
function createPair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

// 正确:TypeScript 可以推断类型
const pair1 = createPair(1, "one"); // 类型为 [number, string]

// 错误:TypeScript 无法推断类型
// const pair2 = createPair({}, {}); // 类型为 [{}, {}],可能不是预期的类型

// 正确:显式指定类型
interface User {
  name: string;
}

interface Product {
  id: number;
}

const pair3 = createPair<User, Product>({ name: "John" }, { id: 1 }); // 类型为 [User, Product]

4. 泛型方法的 this 指向错误

在泛型类的方法中,要注意 this 的指向。

typescript
class Container<T> {
  private value: T;

  constructor(value: T) {
    this.value = value;
  }

  // 错误:箭头函数中的 this 不是指向类实例
  // getValue = (): T => {
  //   return this.value;
  // };

  // 正确:使用普通方法
  getValue(): T {
    return this.value;
  }
}

const container = new Container<number>(42);
console.log(container.getValue()); // 输出:42

5. 泛型工具类型使用错误

使用泛型工具类型时,要确保类型参数正确。

typescript
interface User {
  id: number;
  name: string;
  email: string;
}

// 错误:Pick 的第二个参数必须是 T 的键
// type UserInfo = Pick<User, "name" | "age">; // 类型 "age" 不能分配给类型 keyof User

// 正确:Pick 的第二个参数必须是 T 的键
Type UserInfo = Pick<User, "name" | "email">;

10. 实际应用场景

1. 通用数据结构

使用泛型实现通用数据结构,如栈、队列、链表等。

typescript
// 泛型栈
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;
  }

  size(): number {
    return this.items.length;
  }
}

// 使用
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
numberStack.push(3);
console.log(numberStack.pop()); // 输出:3
console.log(numberStack.peek()); // 输出:2

const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");
console.log(stringStack.pop()); // 输出:world

2. 通用工具函数

使用泛型实现通用工具函数,如排序、过滤、映射等。

typescript
// 泛型排序函数
function sort<T>(array: T[], comparator: (a: T, b: T) => number): T[] {
  return [...array].sort(comparator);
}

// 使用
const numbers = [3, 1, 4, 1, 5, 9, 2, 6];
const sortedNumbers = sort(numbers, (a, b) => a - b);
console.log(sortedNumbers); // 输出:[1, 1, 2, 3, 4, 5, 6, 9]

const strings = ["banana", "apple", "cherry"];
const sortedStrings = sort(strings, (a, b) => a.localeCompare(b));
console.log(sortedStrings); // 输出:['apple', 'banana', 'cherry']

// 泛型过滤函数
function filter<T>(array: T[], predicate: (item: T) => boolean): T[] {
  return array.filter(predicate);
}

// 使用
const evenNumbers = filter(numbers, (n) => n % 2 === 0);
console.log(evenNumbers); // 输出:[4, 2, 6]

// 泛型映射函数
function map<T, U>(array: T[], mapper: (item: T) => U): U[] {
  return array.map(mapper);
}

// 使用
const squaredNumbers = map(numbers, (n) => n * n);
console.log(squaredNumbers); // 输出:[9, 1, 16, 1, 25, 81, 4, 36]

3. 通用服务类

使用泛型实现通用服务类,如 API 服务、存储服务等。

typescript
// 泛型 API 服务
class ApiService<T> {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async get(id: number): Promise<T> {
    const response = await fetch(`${this.baseUrl}/${id}`);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }

  async getAll(): Promise<T[]> {
    const response = await fetch(this.baseUrl);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }

  async create(data: T): Promise<T> {
    const response = await fetch(this.baseUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }

  async update(id: number, data: T): Promise<T> {
    const response = await fetch(`${this.baseUrl}/${id}`, {
      method: "PUT",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }

  async delete(id: number): Promise<void> {
    const response = await fetch(`${this.baseUrl}/${id}`, {
      method: "DELETE"
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
  }
}

// 使用
interface User {
  id: number;
  name: string;
  email: string;
}

const userService = new ApiService<User>("https://api.example.com/users");

// 获取用户
userService.get(1).then(user => {
  console.log(user);
});

// 创建用户
userService.create({ id: 0, name: "John", email: "john@example.com" }).then(user => {
  console.log(user);
});

4. 通用状态管理

使用泛型实现通用状态管理,如 Redux store、Context API 等。

typescript
// 泛型状态管理
interface State<T> {
  data: T | null;
  isLoading: boolean;
  error: string | null;
}

interface Action<T> {
  type: string;
  payload?: T;
  error?: string;
}

function createReducer<T>(initialState: State<T>) {
  return (state: State<T> = initialState, action: Action<T>): State<T> => {
    switch (action.type) {
      case "FETCH_START":
        return {
          ...state,
          isLoading: true,
          error: null
        };
      case "FETCH_SUCCESS":
        return {
          ...state,
          data: action.payload as T,
          isLoading: false,
          error: null
        };
      case "FETCH_ERROR":
        return {
          ...state,
          isLoading: false,
          error: action.error || "An error occurred"
        };
      default:
        return state;
    }
  };
}

// 使用
interface User {
  id: number;
  name: string;
  email: string;
}

const initialState: State<User> = {
  data: null,
  isLoading: false,
  error: null
};

const userReducer = createReducer<User>(initialState);

// 测试
let state = userReducer(initialState, { type: "FETCH_START" });
console.log(state); // 加载状态

state = userReducer(state, { 
  type: "FETCH_SUCCESS", 
  payload: { id: 1, name: "John", email: "john@example.com" } 
});
console.log(state); // 成功状态

state = userReducer(state, { 
  type: "FETCH_ERROR", 
  error: "Failed to fetch user" 
});
console.log(state); // 错误状态

5. 通用组件(React/Vue)

在 React 或 Vue 中使用泛型创建通用组件。

typescript
// React 泛型组件
import React from 'react';

interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

// 使用
interface User {
  id: number;
  name: string;
  email: string;
}

const users: User[] = [
  { id: 1, name: "John", email: "john@example.com" },
  { id: 2, name: "Jane", email: "jane@example.com" },
  { id: 3, name: "Bob", email: "bob@example.com" }
];

function App() {
  return (
    <List
      items={users}
      keyExtractor={(user) => user.id}
      renderItem={(user) => (
        <div>
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      )}
    />
  );
}

// Vue 泛型组件
// <template>
//   <ul>
//     <li v-for="(item, index) in items" :key="keyExtractor(item)">
//       <slot name="item" :item="item" :index="index"></slot>
//     </li>
//   </ul>
// </template>
//
// <script lang="ts">
// import { defineComponent } from 'vue';
//
// export default defineComponent({
//   name: 'List',
//   props: {
//     items: {
//       type: Array as () => any[],
//       required: true
//     },
//     keyExtractor: {
//       type: Function as () => (item: any) => string | number,
//       required: true
//     }
//   }
// });
// </script>

总结

TypeScript 中的泛型是一种强大的特性,它允许我们编写可重用的代码,可以适用于多种类型,而不是单一类型。本文介绍了 TypeScript 泛型的以下内容:

  • 泛型的基本概念:什么是泛型,为什么使用泛型
  • 泛型函数:基本用法,多个泛型参数,泛型约束,泛型参数默认类型
  • 泛型类:基本用法,多个泛型参数,泛型约束,泛型参数默认类型
  • 泛型接口:基本用法,泛型接口作为函数类型,泛型接口继承
  • 泛型约束:基本约束,多重约束,约束为类
  • 泛型工具类型Partial<T>, Required<T>, Readonly<T>, Record<K, T>, Pick<T, K>, Omit<T, K>, Exclude<T, U>, Extract<T, U>, NonNullable<T>, Parameters<T>, ReturnType<T>
  • 泛型的高级用法:递归泛型,条件类型,映射类型
  • 泛型的最佳实践:使用有意义的泛型参数名,使用泛型约束,使用泛型默认类型,合理使用泛型工具类型,避免过度使用泛型
  • 常见错误:泛型参数未使用,泛型约束不满足,泛型类型推断错误,泛型方法的 this 指向错误,泛型工具类型使用错误
  • 实际应用场景:通用数据结构,通用工具函数,通用服务类,通用状态管理,通用组件

通过合理使用泛型,你可以在 TypeScript 中编写更加灵活、可重用、类型安全的代码,提高代码的质量和可维护性。