折腾侠
技术教程

TypeScript 泛型高级用法:从入门到实战

折腾侠
2026/04/25 发布
0约 7 分钟1010 字 / 907 词00

TypeScript 泛型高级用法:从入门到实战

引言

在 TypeScript 的类型系统中,泛型(Generics)是最强大也最容易被误解的特性之一。许多开发者只停留在基础用法层面,却不知道泛型能够解决多少实际问题。本文将深入探讨 TypeScript 泛型的高级用法,帮助你写出更类型安全、更灵活的代码。

什么是泛型?

泛型的本质是"类型参数化"。就像函数可以接受参数一样,泛型允许我们给类型也传递参数。这样,同一个类型定义可以适用于多种不同的具体类型。

最基础的泛型示例:

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

// 使用
const result1 = identity<string>("hello");  // string
const result2 = identity<number>(42);       // number

但这只是冰山一角。让我们深入探索泛型的高级用法。

一、泛型约束(Generic Constraints)

在实际开发中,我们经常需要限制泛型参数的范围。这时就需要用到泛型约束。

基础约束

使用 INLINE_CODE_0 关键字可以约束泛型必须满足某个接口或类型:

TypeScript
interface Lengthwise {
  length: number;
}

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

// ✅ 正确
loggingIdentity("hello");           // string 有 length
loggingIdentity([1, 2, 3]);         // array 有 length
loggingIdentity({ length: 10 });    // 对象有 length 属性

// ❌ 错误
loggingIdentity(42);                // number 没有 length

多约束条件

TypeScript 4.0+ 支持更复杂的约束组合:

TypeScript
interface Identifiable {
  id: string | number;
}

interface Timestamped {
  createdAt: Date;
}

// 同时满足多个约束
function processEntity<T extends Identifiable & Timestamped>(entity: T): T {
  console.log(`Processing entity ${entity.id} created at ${entity.createdAt}`);
  return entity;
}

const user = {
  id: "user-123",
  createdAt: new Date(),
  name: "Alice"
};

processEntity(user);  // ✅ 正确

二、条件类型与泛型

条件类型是 TypeScript 类型系统的"三元运算符",与泛型结合使用可以实现强大的类型推导。

基础条件类型

TypeScript
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;   // true
type B = IsString<42>;        // false

实用工具类型实现

TypeScript 内置的许多工具类型都是基于条件类型实现的。让我们看看如何自己实现:

TypeScript
// 实现 MyExclude
type MyExclude<T, U> = T extends U ? never : T;

type Result = MyExclude<"a" | "b" | "c", "a">;  // "b" | "c"

// 实现 MyPick
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P];
};

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

type BasicUser = MyPick<User, "id" | "name">;
// 等价于 { id: number; name: string; }

// 实现 MyOmit
type MyOmit<T, K extends keyof T> = MyPick<T, MyExclude<keyof T, K>>;

type UserWithoutAge = MyOmit<User, "age">;
// 等价于 { id: number; name: string; email: string; }

三、映射类型(Mapped Types)

映射类型允许我们基于已有类型创建新类型,通过遍历类型的属性键来实现。

基础映射类型

TypeScript
// 将所有属性变为可选
type Partial<T> = {
  [P in keyof T]?: T[P];
};

// 将所有属性变为只读
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

// 将所有属性变为必填
type Required<T> = {
  [P in keyof T]-?: T[P];
};

高级映射类型

结合条件类型和映射类型,可以实现更复杂的转换:

TypeScript
// 将对象的字符串属性变为可选,其他属性保持不变
type PartialStringProps<T> = {
  [P in keyof T as T[P] extends string ? P : never]?: T[P];
} & {
  [P in keyof T as T[P] extends string ? never : P]: T[P];
};

interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  inStock: boolean;
}

type PartialStringProduct = PartialStringProps<Product>;
// 结果:{ id: number; price: number; inStock: boolean; name?: string; description?: string; }

四、模板字面量类型

TypeScript 4.1 引入的模板字面量类型可以与泛型结合,实现动态类型生成。

TypeScript
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Resource = "users" | "posts" | "comments";

// 生成所有可能的 API 端点类型
type ApiEndpoint = `${HttpMethod} /${Resource}`;

// 结果:"GET /users" | "GET /posts" | "GET /comments" | "POST /users" | ...

// 生成事件处理器类型
type EventName<T extends string> = `${T}Changed` | `${T}Updated`;

type UserEvents = EventName<"user">;
// 结果:"userChanged" | "userUpdated"

五、实战应用:API 响应类型

在实际项目中,泛型可以帮助我们构建类型安全的 API 层。

TypeScript
// 定义通用 API 响应结构
interface ApiResponse<T> {
  success: boolean;
  data?: T;
  error?: {
    code: string;
    message: string;
  };
  pagination?: {
    page: number;
    pageSize: number;
    total: number;
  };
}

// 定义用户类型
interface User {
  id: number;
  name: string;
  email: string;
}

// 使用泛型定义具体的 API 响应
type UserResponse = ApiResponse<User>;
type UserListResponse = ApiResponse<User[]>;

// 泛型 API 客户端
class ApiClient {
  private baseUrl: string;
  
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }
  
  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    return response.json();
  }
  
  async post<T>(endpoint: string, data: unknown): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
    return response.json();
  }
}

// 使用示例
const api = new ApiClient("https://api.example.com");

// 获取单个用户 - 类型自动推断
const userResult = await api.get<User>("/users/1");
if (userResult.success && userResult.data) {
  console.log(userResult.data.name);  // 类型安全
}

// 获取用户列表
const usersResult = await api.get<User[]>("/users");
if (usersResult.success && usersResult.data) {
  usersResult.data.forEach(user => {
    console.log(user.email);  // 类型安全
  });
}

六、实战应用:状态管理模式

泛型在状态管理中同样大有用武之地:

TypeScript
// 定义通用 Action 类型
interface Action<T extends string, P = undefined> {
  type: T;
  payload?: P;
}

// 定义用户相关 Actions
type UserAction =
  | Action<"USER_LOGIN", { userId: number; token: string }>
  | Action<"USER_LOGOUT">
  | Action<"USER_UPDATE", Partial<User>>;

// 定义通用 Reducer 类型
type Reducer<State, Actions extends Action<string, any>> = (
  state: State,
  action: Actions
) => State;

// 用户状态
interface UserState {
  isLoggedIn: boolean;
  user: User | null;
  token: string | null;
}

// 用户 Reducer
const userReducer: Reducer<UserState, UserAction> = (state, action) => {
  switch (action.type) {
    case "USER_LOGIN":
      return {
        ...state,
        isLoggedIn: true,
        user: { id: action.payload.userId, name: "", email: "" },
        token: action.payload.token,
      };
    case "USER_LOGOUT":
      return {
        ...state,
        isLoggedIn: false,
        user: null,
        token: null,
      };
    case "USER_UPDATE":
      return {
        ...state,
        user: state.user ? { ...state.user, ...action.payload } : null,
      };
    default:
      return state;
  }
};

七、高级技巧:类型推断优化

TypeScript 提供了多种方式来优化泛型的类型推断。

使用 infer 关键字

TypeScript
// 从 Promise 类型中提取内部类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>;  // string
type B = UnwrapPromise<number>;           // number

// 从函数类型中提取参数和返回类型
type FirstParameter<T extends (...args: any[]) => any> = 
  T extends (arg: infer P, ...args: any[]) => any ? P : never;

type ReturnType<T extends (...args: any[]) => any> = 
  T extends (...args: any[]) => infer R ? R : never;

function greet(name: string): string {
  return `Hello, ${name}`;
}

type Param = FirstParameter<typeof greet>;  // string
type Return = ReturnType<typeof greet>;     // string

泛型默认值

TypeScript 2.3+ 支持泛型默认值:

TypeScript
interface Container<T = string> {
  value: T;
}

const strContainer: Container = { value: "hello" };      // Container<string>
const numContainer: Container<number> = { value: 42 };   // Container<number>

结语

TypeScript 泛型是构建类型安全应用的强大工具。从基础的类型参数化,到复杂的条件类型、映射类型,泛型提供了丰富的表达能力。掌握这些高级用法,可以让你:

  1. 减少重复代码:一套类型定义适配多种场景
  2. 提高类型安全:编译时捕获更多错误
  3. 增强代码可读性:类型即文档
  4. 提升开发体验:更好的智能提示和自动补全

记住,泛型不是炫技的工具,而是解决实际问题的利器。在实际项目中,应该根据需求选择合适的泛型复杂度,在类型安全和代码可读性之间找到平衡点。

开始在你的项目中使用这些泛型技巧吧,你会发现 TypeScript 的类型系统远比想象中更强大!

分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...