Chenyj Space
返回博客
·43 分钟

TypeScript 泛型深入指南:从基础到高级

TypeScript泛型类型系统前端前端

泛型基础

什么是泛型

泛型(Generics)是一种参数化类型,允许你创建可重用的组件,这些组件可以支持多种类型而不是单一类型。

typescript
// 没有泛型:需要为每种类型写重复代码
function identityNumber(arg: number): number {
  return arg;
}

function identityString(arg: string): string {
  return arg;
}

// 使用泛型:一个函数支持多种类型
function identity<T>(arg: T): T {
  return arg;
}

// 使用
const num = identity<number>(42);
const str = identity<string>("hello");
const bool = identity<boolean>(true);

类型推断

typescript
function identity<T>(arg: T): T {
  return arg;
}

// TypeScript 可以推断类型
const num = identity(42);      // 推断为 number
const str = identity("hello"); // 推断为 string

// 显式指定类型
const explicit = identity<string>("hello");

多个泛型参数

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

const p = pair<string, number>("hello", 42);
const p2 = pair("hello", 42);  // 类型推断

// 实际应用
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
  return array.map(fn);
}

const numbers = [1, 2, 3];
const strings = map(numbers, n => n.toString());

泛型约束

基本约束

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

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

// 有效
logLength("hello");        // string 有 length
logLength([1, 2, 3]);      // array 有 length
logLength({ length: 10 }); // 对象有 length

// 无效
logLength(42);             // number 没有 length
logLength(true);           // boolean 没有 length

keyof 约束

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

const person = { name: "Alice", age: 30 };

const name = getProperty(person, "name");  // string
const age = getProperty(person, "age");    // number

// 错误:'email' 不是 person 的属性
// const email = getProperty(person, "email");

类型约束

typescript
// 约束为特定类型
function createArray<T extends string | number>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

const strings = createArray(3, "hello");  // string[]
const numbers = createArray(3, 42);       // number[]

// 错误:boolean 不满足约束
// const bools = createArray(3, true);

泛型接口和类

泛型接口

typescript
// 泛型接口
interface Box<T> {
  value: T;
  getValue(): T;
  setValue(value: T): void;
}

// 实现
class NumberBox implements Box<number> {
  constructor(private _value: number) {}
  
  getValue(): number {
    return this._value;
  }
  
  setValue(value: number): void {
    this._value = value;
  }
}

// 泛型实现
class GenericBox<T> implements Box<T> {
  constructor(private _value: T) {}
  
  getValue(): T {
    return this._value;
  }
  
  setValue(value: T): void {
    this._value = value;
  }
}

const box = new GenericBox<string>("hello");
box.getValue();  // "hello"
box.setValue("world");

泛型类

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.pop();  // 2

const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");

泛型接口作为函数类型

typescript
interface SearchFunc<T> {
  (source: T[], target: T): number;
}

const linearSearch: SearchFunc<number> = (source, target) => {
  for (let i = 0; i < source.length; i++) {
    if (source[i] === target) return i;
  }
  return -1;
};

const binarySearch: SearchFunc<number> = (source, target) => {
  let left = 0;
  let right = source.length - 1;
  
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (source[mid] === target) return mid;
    if (source[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  
  return -1;
};

泛型工具类型

Partial 和 Required

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

// Partial:所有属性变为可选
type PartialUser = Partial<User>;
// 等价于:
// {
//   id?: number;
//   name?: string;
//   email?: string;
//   age?: number;
// }

// Required:所有属性变为必选
type RequiredUser = Required<PartialUser>;
// 等价于 User

// 实际应用
function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates };
}

const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  age: 30
};

const updated = updateUser(user, { age: 31 });

Readonly

typescript
// Readonly:所有属性变为只读
type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  age: 30
};

// 错误:只读属性不能修改
// user.name = "Bob";

// 实际应用:不可变数据
function createUser(data: Readonly<User>): User {
  return { ...data };
}

Pick 和 Omit

typescript
// Pick:选取部分属性
type UserBasic = Pick<User, "id" | "name">;
// 等价于:
// {
//   id: number;
//   name: string;
// }

// Omit:排除部分属性
type UserWithoutEmail = Omit<User, "email">;
// 等价于:
// {
//   id: number;
//   name: string;
//   age: number;
// }

// 实际应用
function getUserBasic(id: number): UserBasic {
  return { id, name: "Alice" };
}

function createUserProfile(data: Omit<User, "id">): User {
  return { id: Date.now(), ...data };
}

Record

typescript
// Record:构造键值对类型
type UserRoles = Record<string, string[]>;

const roles: UserRoles = {
  admin: ["read", "write", "delete"],
  user: ["read"],
  guest: []
};

// 实际应用
type PageInfo = {
  title: string;
  url: string;
};

type Pages = Record<"home" | "about" | "contact", PageInfo>;

const pages: Pages = {
  home: { title: "Home", url: "/" },
  about: { title: "About", url: "/about" },
  contact: { title: "Contact", url: "/contact" }
};

Exclude 和 Extract

typescript
type Status = "active" | "inactive" | "pending" | "deleted";

// Exclude:从联合类型中排除
type ActiveStatus = Exclude<Status, "deleted" | "inactive">;
// 等价于 "active" | "pending"

// Extract:从联合类型中提取
type NonActiveStatus = Extract<Status, "inactive" | "deleted">;
// 等价于 "inactive" | "deleted"

// 实际应用
function activateUser(status: Exclude<Status, "deleted">) {
  // 处理激活逻辑
}

NonNullable

typescript
type MaybeString = string | null | undefined;

// NonNullable:排除 null 和 undefined
type DefinitelyString = NonNullable<MaybeString>;
// 等价于 string

// 实际应用
function processValue(value: NonNullable<MaybeString>) {
  // value 保证是 string
  console.log(value.toUpperCase());
}

高级泛型模式

条件类型

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

type A = IsString<string>;   // true
type B = IsString<number>;   // false

// 实际应用
type ArrayOrSingle<T> = T extends any[] ? T : T[];

function ensureArray<T>(value: T): ArrayOrSingle<T> {
  return Array.isArray(value) ? value : [value] as any;
}

const arr1 = ensureArray([1, 2, 3]);  // number[]
const arr2 = ensureArray(42);          // number[]

映射类型

typescript
// 映射类型
type Optional<T> = {
  [K in keyof T]?: T[K];
};

type Readonly2<T> = {
  readonly [K in keyof T]: T[K];
};

// 实际应用:表单状态
type FormState<T> = {
  [K in keyof T]: {
    value: T[K];
    error: string | null;
    touched: boolean;
  };
};

interface LoginForm {
  username: string;
  password: string;
}

type LoginFormState = FormState<LoginForm>;
// 等价于:
// {
//   username: { value: string; error: string | null; touched: boolean; };
//   password: { value: string; error: string | null; touched: boolean; };
// }

模板字面量类型

typescript
// 模板字面量类型
type EventName = "click" | "scroll" | "mousemove";
type EventHandlerName = `on${Capitalize<EventName>}`;
// 等价于 "onClick" | "onScroll" | "onMousemove"

// 实际应用
type CSSProperty = "margin" | "padding" | "border";
type CSSDirection = "top" | "right" | "bottom" | "left";
type CSSClassName = `${CSSProperty}-${CSSDirection}`;
// 等价于 "margin-top" | "margin-right" | ... | "border-left"

// 构建 API 路径
type ApiVersion = "v1" | "v2";
type ApiResource = "users" | "posts" | "comments";
type ApiPath = `/api/${ApiVersion}/${ApiResource}`;
// 等价于 "/api/v1/users" | "/api/v1/posts" | ... | "/api/v2/comments"

infer 关键字

typescript
// infer:在条件类型中推断
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = ReturnType<() => string>;           // string
type B = ReturnType<(x: number) => boolean>; // boolean

// 推断函数参数
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

type C = Parameters<(a: string, b: number) => void>; // [string, number]

// 推断 Promise 类型
type Awaited<T> = T extends Promise<infer U> ? U : T;

type D = Awaited<Promise<string>>;  // string
type E = Awaited<Promise<number>>;  // number

// 实际应用
function createPromise<T>(value: T): Promise<T> {
  return Promise.resolve(value);
}

type PromiseType<T> = T extends Promise<infer U> ? U : never;

const promise = createPromise("hello");
type Result = PromiseType<typeof promise>;  // string

实际应用模式

API 响应类型

typescript
// 通用 API 响应
interface ApiResponse<T> {
  success: boolean;
  data: T;
  error?: string;
  timestamp: number;
}

// 具体响应类型
type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;
type ErrorResponse = ApiResponse<null>;

// API 函数
async function fetchUser(id: number): Promise<UserResponse> {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

async function fetchUsers(): Promise<UsersResponse> {
  const response = await fetch("/api/users");
  return response.json();
}

分页类型

typescript
interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
  };
}

type PaginatedUsers = PaginatedResponse<User>;

async function fetchUsers(
  page: number = 1,
  pageSize: number = 10
): Promise<PaginatedUsers> {
  const response = await fetch(
    `/api/users?page=${page}&pageSize=${pageSize}`
  );
  return response.json();
}

表单状态管理

typescript
interface FormField<T> {
  value: T;
  error: string | null;
  touched: boolean;
  dirty: boolean;
}

type FormState<T> = {
  [K in keyof T]: FormField<T[K]>;
};

interface LoginForm {
  username: string;
  password: string;
  rememberMe: boolean;
}

type LoginFormState = FormState<LoginForm>;

function createFormState<T>(initialValues: T): FormState<T> {
  const state = {} as FormState<T>;
  
  for (const key in initialValues) {
    state[key] = {
      value: initialValues[key],
      error: null,
      touched: false,
      dirty: false
    };
  }
  
  return state;
}

const loginFormState = createFormState<LoginForm>({
  username: "",
  password: "",
  rememberMe: false
});

事件系统

typescript
// 事件类型映射
interface EventMap {
  click: { x: number; y: number };
  scroll: { top: number; left: number };
  resize: { width: number; height: number };
}

// 事件监听器
class EventEmitter<T extends Record<string, any>> {
  private listeners: {
    [K in keyof T]?: Array<(event: T[K]) => void>;
  } = {};
  
  on<K extends keyof T>(event: K, listener: (event: T[K]) => void): void {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event]!.push(listener);
  }
  
  off<K extends keyof T>(event: K, listener: (event: T[K]) => void): void {
    const listeners = this.listeners[event];
    if (listeners) {
      this.listeners[event] = listeners.filter(l => l !== listener);
    }
  }
  
  emit<K extends keyof T>(event: K, data: T[K]): void {
    const listeners = this.listeners[event];
    if (listeners) {
      listeners.forEach(listener => listener(data));
    }
  }
}

// 使用
const emitter = new EventEmitter<EventMap>();

emitter.on("click", (event) => {
  console.log(`Clicked at ${event.x}, ${event.y}`);
});

emitter.on("resize", (event) => {
  console.log(`Resized to ${event.width}x${event.height}`);
});

emitter.emit("click", { x: 10, y: 20 });

状态机

typescript
// 状态机类型
type StateMachine<S extends string, E extends string> = {
  initial: S;
  states: {
    [K in S]: {
      on?: {
        [K2 in E]?: S;
      };
      entry?: () => void;
      exit?: () => void;
    };
  };
};

// 创建状态机
function createStateMachine<S extends string, E extends string>(
  config: StateMachine<S, E>
) {
  let currentState = config.initial;
  
  return {
    getState: () => currentState,
    send: (event: E) => {
      const state = config.states[currentState];
      const nextState = state.on?.[event];
      
      if (nextState) {
        state.exit?.();
        currentState = nextState;
        config.states[nextState].entry?.();
      }
    }
  };
}

// 使用
type TrafficLightState = "red" | "yellow" | "green";
type TrafficLightEvent = "next" | "emergency";

const trafficLight = createStateMachine<TrafficLightState, TrafficLightEvent>({
  initial: "red",
  states: {
    red: {
      on: { next: "green" },
      entry: () => console.log("Stop!")
    },
    green: {
      on: { next: "yellow" },
      entry: () => console.log("Go!")
    },
    yellow: {
      on: { next: "red" },
      entry: () => console.log("Caution!")
    }
  }
});

trafficLight.send("next");  // "Go!"
trafficLight.send("next");  // "Caution!"

最佳实践

1. 有意义的命名

typescript
// ❌ 不好:单字母
function process<T>(data: T): T { ... }

// ✅ 好:有意义的名称
function processData<TData>(data: TData): TData { ... }

// ✅ 好:描述约束
function findItem<TItem extends { id: number }>(
  items: TItem[],
  id: number
): TItem | undefined { ... }

2. 合理使用约束

typescript
// ❌ 过度约束
function process<T extends { a: number; b: string; c: boolean }>(obj: T) { ... }

// ✅ 合理约束
function process<T extends Record<string, unknown>>(obj: T) { ... }

// ✅ 精确约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { ... }

3. 避免过度泛型

typescript
// ❌ 不必要
function add<T extends number>(a: T, b: T): number {
  return a + b;
}

// ✅ 直接
function add(a: number, b: number): number {
  return a + b;
}

4. 工具类型组合

typescript
// 组合使用
type UserUpdate = Partial<Omit<User, "id">> & { id: number };

// 实际应用
function updateUser(user: UserUpdate): User {
  // ...
}

总结

核心概念

  1. 泛型函数function identity<T>(arg: T): T
  2. 泛型约束T extends HasLength
  3. 泛型接口/类interface Box<T> { ... }
  4. 工具类型Partial<T>, Pick<T, K>, Record<K, V>

使用场景

  1. 通用函数:支持多种类型
  2. 类型安全的容器:Array、Map、Set
  3. API 响应:通用响应类型
  4. 状态管理:类型安全的状态

学习资源


延伸阅读