05 - 泛型

4 阅读4分钟

泛型是 TypeScript 最强大的特性之一。它让你写出可复用类型安全的代码。


5.1 为什么需要泛型?

假设要写一个"返回数组第一个元素"的函数:

// 方案 1:写死类型 → 不通用
function firstNumber(arr: number[]): number {
  return arr[0];
}

// 方案 2:用 any → 丢失类型信息
function firstAny(arr: any[]): any {
  return arr[0];
}
const result = firstAny([1, 2, 3]); // result 是 any,不知道是 number

// 方案 3:用泛型 → 既通用又安全 ✅
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}

const a = first([1, 2, 3]);       // a: number
const b = first(["x", "y"]);      // b: string
const c = first([true, false]);    // c: boolean

<T> 就是类型参数,调用时 TypeScript 会自动推断 T 是什么类型。


5.2 泛型函数

// 单个类型参数
function identity<T>(value: T): T {
  return value;
}

identity(42);       // T 推断为 number
identity("hello");  // T 推断为 string

// 多个类型参数
function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}

const p = pair("name", 25); // [string, number]

// 显式指定类型参数
const n = identity<number>(42);

5.3 泛型接口

// 泛型接口
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// 使用时指定 T
const userRes: ApiResponse<{ name: string; age: number }> = {
  code: 200,
  message: "success",
  data: { name: "张三", age: 25 },
};

const listRes: ApiResponse<string[]> = {
  code: 200,
  message: "success",
  data: ["a", "b", "c"],
};

泛型函数接口

interface Transformer<Input, Output> {
  (value: Input): Output;
}

const stringify: Transformer<number, string> = (n) => n.toString();
const parse: Transformer<string, number> = (s) => Number(s);

5.4 泛型类

class Container<T> {
  private value: T;

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

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

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

const numContainer = new Container<number>(42);
numContainer.getValue(); // number
numContainer.setValue(100);

const strContainer = new Container("hello");
strContainer.getValue(); // string

实用示例:简单的 EventEmitter

class EventEmitter<Events extends Record<string, any>> {
  private handlers: Partial<Record<keyof Events, Function[]>> = {};

  on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void) {
    if (!this.handlers[event]) {
      this.handlers[event] = [];
    }
    this.handlers[event]!.push(handler);
  }

  emit<K extends keyof Events>(event: K, data: Events[K]) {
    this.handlers[event]?.forEach((fn) => fn(data));
  }
}

// 定义事件类型
interface AppEvents {
  login: { userId: string };
  logout: undefined;
  message: { text: string; from: string };
}

const emitter = new EventEmitter<AppEvents>();
emitter.on("login", (data) => {
  console.log(data.userId); // ✅ TS 知道 data 的类型
});
emitter.on("message", (data) => {
  console.log(data.text); // ✅
});

5.5 泛型约束(extends)

限制类型参数必须满足某些条件:

// T 必须有 length 属性
function logLength<T extends { length: number }>(value: T): void {
  console.log(value.length);
}

logLength("hello");     // ✅ string 有 length
logLength([1, 2, 3]);   // ✅ 数组有 length
logLength({ length: 5 }); // ✅
logLength(42);          // ❌ number 没有 length

keyof 约束

// K 必须是 T 的属性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "张三", age: 25, email: "a@b.com" };

getProperty(user, "name");  // ✅ 返回 string
getProperty(user, "age");   // ✅ 返回 number
getProperty(user, "phone"); // ❌ "phone" 不在 User 的键中

多重约束

interface HasId {
  id: number;
}

interface HasName {
  name: string;
}

// T 必须同时满足 HasId 和 HasName
function display<T extends HasId & HasName>(item: T): string {
  return `#${item.id}: ${item.name}`;
}

5.6 泛型默认类型

// 默认 T 为 string
interface Container<T = string> {
  value: T;
}

const a: Container = { value: "hello" };        // T = string
const b: Container<number> = { value: 42 };     // T = number

// 函数也可以有默认类型
function createArray<T = number>(length: number, fill: T): T[] {
  return new Array(length).fill(fill);
}

5.7 内置工具类型

TypeScript 提供了很多基于泛型的内置工具类型,非常实用:

Partial<T> —— 所有属性变可选

interface User {
  name: string;
  age: number;
  email: string;
}

type PartialUser = Partial<User>;
// 等同于:
// {
//   name?: string;
//   age?: number;
//   email?: string;
// }

// 常用于更新操作
function updateUser(id: number, updates: Partial<User>): void {
  // 只需要传要更新的字段
}

updateUser(1, { name: "新名字" }); // ✅ 不用传所有字段

Required<T> —— 所有属性变必选

interface Config {
  host?: string;
  port?: number;
}

type RequiredConfig = Required<Config>;
// { host: string; port: number; }

Readonly<T> —— 所有属性变只读

type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = { name: "张三", age: 25, email: "a@b.com" };
user.name = "李四"; // ❌ 只读

Pick<T, K> —— 挑选部分属性

type UserBasic = Pick<User, "name" | "age">;
// { name: string; age: number; }

Omit<T, K> —— 排除部分属性

type UserWithoutEmail = Omit<User, "email">;
// { name: string; age: number; }

Record<K, V> —— 创建键值映射

type UserRole = "admin" | "user" | "guest";
type Permissions = Record<UserRole, string[]>;
// {
//   admin: string[];
//   user: string[];
//   guest: string[];
// }

Extract<T, U> 和 Exclude<T, U>

type A = "a" | "b" | "c" | "d";

type OnlyAB = Extract<A, "a" | "b">;  // "a" | "b"
type NotAB = Exclude<A, "a" | "b">;   // "c" | "d"

NonNullable<T>

type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>; // string

ReturnType<T> 和 Parameters<T>

function fetchData(url: string, timeout: number): Promise<any> {
  return fetch(url);
}

type FetchReturn = ReturnType<typeof fetchData>;     // Promise<any>
type FetchParams = Parameters<typeof fetchData>;     // [string, number]

5.8 泛型实战模式

工厂函数

function createInstance<T>(constructor: new () => T): T {
  return new constructor();
}

类型安全的 API 请求

async function request<T>(url: string, options?: RequestInit): Promise<T> {
  const res = await fetch(url, options);
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  return res.json() as Promise<T>;
}

// 使用时指定返回类型
interface User {
  id: number;
  name: string;
}

const user = await request<User>("/api/user/1");
console.log(user.name); // ✅ TS 知道有 name 属性

管道/链式调用

function pipe<A, B>(fn1: (a: A) => B): (a: A) => B;
function pipe<A, B, C>(fn1: (a: A) => B, fn2: (b: B) => C): (a: A) => C;
function pipe<A, B, C, D>(
  fn1: (a: A) => B,
  fn2: (b: B) => C,
  fn3: (c: C) => D,
): (a: A) => D;
function pipe(...fns: Function[]) {
  return (input: any) => fns.reduce((acc, fn) => fn(acc), input);
}

const transform = pipe(
  (s: string) => s.length,   // string → number
  (n: number) => n > 5,      // number → boolean
);

transform("hello world"); // true

📝 练习

  1. 实现一个泛型函数 last<T>(arr: T[]): T | undefined
  2. 实现 merge<A, B>(a: A, b: B): A & B
  3. 用泛型约束实现 pluck<T, K extends keyof T>(arr: T[], key: K): T[K][]
  4. 用内置工具类型:给 User 创建 CreateUserDTO(不包含 id)和 UpdateUserDTO(所有字段可选)
// 参考答案

// 1
function last<T>(arr: T[]): T | undefined {
  return arr[arr.length - 1];
}

// 2
function merge<A extends object, B extends object>(a: A, b: B): A & B {
  return { ...a, ...b };
}

// 3
function pluck<T, K extends keyof T>(arr: T[], key: K): T[K][] {
  return arr.map((item) => item[key]);
}

const users = [
  { name: "张三", age: 25 },
  { name: "李四", age: 30 },
];
pluck(users, "name"); // ["张三", "李四"],类型 string[]

// 4
interface User {
  id: number;
  name: string;
  age: number;
  email: string;
}

type CreateUserDTO = Omit<User, "id">;
type UpdateUserDTO = Partial<Omit<User, "id">>;