Skip to content
第 7 章 前端 ⏱ 12 分钟阅读

第 7 章:泛型 ​

学习目标 ​

  • 理解泛型的本质(类型参数化)
  • 学会函数、接口、类的泛型
  • 掌握泛型约束(extends)
  • 避开默认类型与条件推断的坑

一、为什么需要泛型 ​

typescript
// 不带泛型:写三遍
function getNumber(arg: number): number { return arg; }
function getString(arg: string): string { return arg; }

// 带泛型:写一遍
function identity<T>(arg: T): T {
  return arg;
}

identity<number>(42);    // T = number
identity<string>('Tom'); // T = string
identity(123);           // 推断 T = number

二、泛型函数 ​

typescript
// 数组第一个元素
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

first<number>([1, 2, 3]);          // 1,推断 number
first<string>(['a', 'b']);          // 'a'
first([]);                          // undefined

// 多个类型参数
function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

const p = pair<string, number>('age', 18);  // ['age', 18]

⚠️ 坑 1:first<T> 和 first(T) 看起来一样,但只有尖括号版本是泛型;括号是 JS 函数调用。

三、泛型接口 ​

typescript
interface Box<T> {
  value: T;
}

const numberBox: Box<number> = { value: 42 };
const stringBox: Box<string> = { value: 'Tom' };

// API 响应通用类型
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

const userRes: ApiResponse<User> = {
  code: 0,
  message: 'ok',
  data: { id: 1, name: 'Tom' },
};

四、泛型类 ​

typescript
class Stack<T> {
  private items: T[] = [];

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

  pop(): T | undefined {
    return this.items.pop();
  }
}

const numStack = new Stack<number>();
numStack.push(1);
numStack.push('Tom');  // ❌ 类型不匹配

⚠️ 坑 2:泛型类的静态成员不能使用类型参数:

typescript
class Box<T> {
  static value: T;  // ❌ Static members cannot reference class type parameters
  static fn(x: T): void {}  // ❌
}

五、泛型约束(extends) ​

typescript
// 约束必须有 length 属性
interface HasLength {
  length: number;
}

function logLength<T extends HasLength>(arg: T): void {
  console.log(arg.length);
}

logLength('hello');   // ✅ string 有 length
logLength([1, 2]);   // ✅ array 有 length
logLength(123);      // ❌ number 没有 length

使用 keyof 约束对象 key:

typescript
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { id: 1, name: 'Tom' };
getProp(user, 'name');   // string
getProp(user, 'age');    // ❌ 'age' 不在 keyof T

六、多个约束(交叉) ​

typescript
interface Named { name: string; }
interface Aged { age: number; }

function greet<T extends Named & Aged>(p: T): void {
  console.log(`${p.name}, ${p.age}`);
}

greet({ name: 'Tom', age: 18 });  // ✅
greet({ name: 'Tom' });           // ❌ 缺 age

七、默认类型参数 ​

typescript
interface ApiResponse<T = unknown> {
  code: number;
  data: T;
}

const r1: ApiResponse = { code: 0, data: 'anything' };        // T = unknown
const r2: ApiResponse<User> = { code: 0, data: { id: 1 } };  // T = User

// 函数默认类型
function create<T = string>(): T {
  return '' as T;
}

⚠️ 坑 3:默认类型只在不指定且推断不出时生效;显式传 null 或 undefined 不会触发默认值。

八、条件类型与 infer(预告) ​

typescript
// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = MyReturnType<() => string>;  // string
type B = MyReturnType<() => number>;  // number

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type X = ElementOf<string[]>;     // string

九、实战:通用 CRUD ​

typescript
interface Entity { id: number }

interface Repository<T extends Entity> {
  findById(id: number): T | null;
  save(entity: T): T;
  delete(id: number): void;
}

class UserRepo implements Repository<User> {
  private users: User[] = [];

  findById(id: number): User | null {
    return this.users.find(u => u.id === id) ?? null;
  }

  save(user: User): User {
    this.users.push(user);
    return user;
  }

  delete(id: number): void {
    this.users = this.users.filter(u => u.id !== id);
  }
}

十、本章小结 ​

要点关键
泛型类型参数化,<T> 表示"某种类型"
函数泛型function fn<T>(x: T): T
接口泛型interface Box<T> { value: T }
类泛型class Stack<T> { items: T[] }
约束<T extends Xxx> 限定 T 的范围
默认<T = string> 不传时用默认
静态成员不能用类型参数

动手练习 ​

  1. 通用函数:写 reverse<T>(arr: T[]): T[],支持任意类型数组反转
  2. 泛型接口:定义 Result<T, E = Error>,包含 ok: boolean / value?: T / error?: E
  3. 约束实战:写 pluck<T, K extends keyof T>(objs: T[], key: K): T[K][]

下一章:第 8 章:类与继承 →

本站基于 VitePress 构建 · 由 StackHub 团队维护