🔍 登录 注册

TypeScript 高级类型实战:从泛型到类型体操的完整进阶

Z ZeroIsStart 2026-10-20 前端 👁 1 ⏱ 5min

TypeScript 高级类型实战:从泛型到类型体操的完整进阶

TypeScript 的价值不仅是"给 JS 加类型",而是让类型系统成为编译期的逻辑验证器。本文从泛型、条件类型到工具类型的实现原理,系统提升你的类型设计能力,让"类型即文档、类型即测试"落地到日常开发。

一、泛型:类型的参数化

泛型让类型像函数参数一样可复用:

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

const n = identity(42);        // number
const s = identity("hello");   // string

泛型约束让参数类型更精确:

interface HasLength {
  length: number;
}

function longest<T extends HasLength>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

// longest(42, 1)  // 编译错误:number 没有 length
longest("ab", "cde")          // 正确
longest([1, 2], [3])          // 正确

二、联合类型与类型收窄

type Result = { ok: true; data: string } | { ok: false; error: string };

function handle(r: Result) {
  if (r.ok) {
    r.data.toUpperCase();   // 类型收窄为 { ok: true; data: string }
  } else {
    r.error.toLowerCase();  // 收窄为失败分支
  }
}

可辨识联合(Discriminated Union) 是处理复杂状态机的利器:每个分支带一个字面量标签字段,switch 即天然收窄。

三、条件类型与 infer:类型编程的核心

3.1 条件类型

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

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

3.2 infer:类型推断

// 提取函数返回类型(就是内置 ReturnType 的实现)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Fn = () => Promise<string>;
type R = MyReturnType<Fn>;    // Promise<string>

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

infer 的价值:让类型系统能从已有类型中"逆向解析"出需要的部分,是类型体操的基本功。

四、映射类型:批量变换类型结构

// 把每个属性变为可空
type Nullable<T> = { [K in keyof T]: T[K] | null };

// 只读版本(内置 Readonly 的实现)
type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

interface User {
  id: number;
  name: string;
}
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null }

五、内置工具类型速查

// 从接口中挑选属性
type PickUser = Pick<User, "id" | "name">;

// 排除属性
type WithoutId = Omit<User, "id">;

// 部分可选 / 全部必填
type PartialUser = Partial<User>;
type RequiredUser = Required<User>;

// 提取/排除联合类型成员
type A = Extract<"a" | "b" | "c", "a" | "b">;   // "a" | "b"
type B = Exclude<"a" | "b" | "c", "a">;         // "b" | "c"

// 函数与类工具
type P = Parameters<(a: number, b: string) => void>;  // [number, string]
type RT = ReturnType<() => boolean>;                  // boolean
type CI = ConstructorParameters<new (a: number) => T>; // [number]

六、实际项目中的高级用法

6.1 类型安全的 API 层

type ApiMap = {
  "/api/v1/articles": { query: { page: number }; res: Article[] };
  "/api/v1/articles/:slug": { params: { slug: string }; res: Article };
};

function get<K extends keyof ApiMap>(
  path: K,
  opts: ApiMap[K]["query"]
): Promise<ApiMap[K]["res"]>;

请求函数与接口定义绑定,改接口时调用方全部编译报错——这就是类型驱动的重构。

6.2 表单校验的精确类型

type FormState<T> = {
  values: T;
  errors: { [K in keyof T]?: string };
  touched: { [K in keyof T]?: boolean };
};

interface LoginForm {
  email: string;
  password: string;
}
// errors.email / errors.password 可访问,errors.username 编译报错

6.3 模板字面量类型

type EventName = `on${Capitalize<"click" | "change" | "submit">}`;
// "onClick" | "onChange" | "onSubmit"

模板字面量类型在事件系统、路径拼装、CSS 属性名等场景非常实用。

七、类型体操的三个实战原则

  1. 先建模后编码:复杂业务先写类型定义,让类型引导实现;
  2. 工具类型优先:80% 需求用内置工具类型解决,别重复造轮子;
  3. 体操克制:类型太复杂反而难维护——如果一段类型代码比业务代码还难读,不如退回显式标注。

检查清单:

  • any 出现的地方必须有理由(迁移期除外);
  • 对外 API 类型全部显式导出;
  • 每个泛型都有意义,不为"看起来高级"而用。

结语

TypeScript 的类型系统是"可编程的",掌握泛型、条件类型、infer 与映射类型,你就能把类型从"标注"升级为"约束"。真正的高手写类型不是为了炫技,而是让编译器替你挡住:拼错的字段、传错的参数、漏掉的 null 分支。当你的项目"类型通过即逻辑通过"成为一种常态,TypeScript 才真正兑现了它的承诺。

读完了?给作者一点支持

本文采用 CC BY 4.0 协议转载 · © ZeroIsStart

评论(0)

登录 后参与评论。

  • 还没有评论,来抢沙发。

🔗 相关文章