TypeScript 高级类型完全指南:泛型、条件类型、映射类型一次吃透

很多人用 TypeScript,停留在"给变量标个类型"的层面 —— 这只用到了它 20% 的能力。TS 真正强大的地方,是它有一套图灵完备的类型系统:你可以在"类型"这个层面做计算、做推导、做变换。这篇把泛型、泛型约束、条件类型、infer、映射类型这几个核心武器讲透,最后你会发现 —— TS 那些内置工具类型(Partial / Pick / Omit…)全是用它们拼出来的,没有任何魔法。

泛型:类型也能当"参数"传

泛型是高级类型的地基。它解决的问题是:怎么写一个"对多种类型都适用、又不丢失类型信息"的函数或类型。

泛型:类型层面的"参数"

  // 不用泛型:要么 any(丢了类型),要么为每种类型写一遍
  function firstAny(arr: any[]): any { return arr[0]; }

  // 用泛型:T 是一个"类型参数",调用时自动推断
  function first<T>(arr: T[]): T { return arr[0]; }

  first([1, 2, 3]);        // T 推断为 number,返回值类型 number
  first(['a', 'b']);       // T 推断为 string,返回值类型 string

  泛型的本质:让"类型"也能像"值"一样被传递和复用。

关键体会:<T> 里的 T 是一个"类型参数",它跟函数的普通参数是一回事 —— 只不过普通参数传的是"值",类型参数传的是"类型"。调用时你甚至不用手写 T 是什么,TS 会根据你传入的实参自动推断。泛型的本质,就是让类型也能被传递、被复用

泛型约束:用 extends 划定范围

纯泛型 <T> 有个问题:T 可以是任何类型,所以在函数体里你不能假设 T 有任何属性 —— 你想访问 x.length,TS 会报错,因为万一传进来的 T 是 number 呢?

解决办法是泛型约束:用 extends 给类型参数划一个范围。

泛型约束:用 extends 给类型参数"划范围"

  // T 可以是任何类型 —— 但这样我们就不能假设它有任何属性
  function logLength<T>(x: T) { console.log(x.length); }  // ✗ 报错

  // 用 extends 约束:T 必须有 length 属性
  function logLength<T extends { length: number }>(x: T) {
    console.log(x.length);   // ✓ OK
    return x;                // 返回值还是精确的 T,不是宽泛的对象
  }
  logLength('hello');        // ✓ string 有 length
  logLength([1, 2, 3]);      // ✓ 数组有 length
  logLength(123);            // ✗ number 没有 length,编译期就报错

T extends { length: number } 的意思是:"T 可以是任何类型,但前提是它得有一个 number 类型的 length 属性"。这样函数体里访问 x.length 就合法了,同时返回值还能保持是精确的 T,而不是宽泛的对象。这里的 extends 表示"约束 / 符合",不是面向对象里的"继承" —— 这是初学最容易混的一点。

条件类型:类型层面的 if-else

有了 extends 表示"符合某个形状",就能做"条件判断"了。条件类型就是类型层面的三元表达式:

条件类型:类型层面的"三元表达式"

  type IsString<T> = T extends string ? true : false;
  type A = IsString<'hello'>;   // true
  type B = IsString<123>;       // false

  配合 infer 还能"提取"类型 —— infer 像一个占位符,
  让 TS 帮你把某个位置的类型推断出来:

  type ElementType<T> = T extends (infer U)[] ? U : T;
  type C = ElementType<number[]>;   // number  —— 提取出了数组元素类型
  type D = ElementType<string>;     // string  —— 不是数组,原样返回

T extends U ? X : Y 读作:"如果 T 符合 U,结果就是 X,否则是 Y"。

条件类型真正的威力,在于它能搭配 infer 关键字。infer U 相当于在类型里放一个占位符,告诉 TS:"这个位置的类型我不知道,你帮我推断出来,推断的结果存到 U 里。"上面 ElementType 的例子,就是用 infernumber[] 里把元素类型 number "抠"了出来。很多复杂的类型工具(比如提取函数返回值类型的内置 ReturnType),核心都是 infer

映射类型:批量改造一个类型

前面都是处理"单个类型",映射类型则是批量处理一个对象类型的所有属性。它的语法核心是 [K in keyof T] —— 遍历 T 的每一个属性键。

映射类型:批量"改造"一个类型的所有属性

  type User = { id: number; name: string; age: number };

  // 把所有属性变成可选 —— 这就是内置的 Partial<T> 的实现原理
  type MyPartial<T> = { [K in keyof T]?: T[K] };
  type PartialUser = MyPartial<User>;
  // { id?: number; name?: string; age?: number }

  // 把所有属性变成只读 —— 内置 Readonly<T> 的原理
  type MyReadonly<T> = { readonly [K in keyof T]: T[K] };

keyof T 拿到 T 所有属性名组成的联合类型,[K in ...]for...in 一样遍历它们。在遍历的过程中,你可以给每个属性加 ?(变可选)、加 readonly(变只读),或者改它的值类型。TS 内置的 Partial<T>Readonly<T>,实现就是上面这两行。

组合起来:亲手实现一个内置工具类型

把泛型、泛型约束、映射类型组合在一起,你就能自己实现 TS 的内置工具类型了。以 Pick 为例:

组合起来:实现一个 Pick(从类型里挑几个属性出来)

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

  type UserNameAndAge = MyPick<User, 'name' | 'age'>;
  // { name: string; age: number }

  这就是 TS 内置工具类型(Partial / Required / Pick / Omit / Record …)
  的真实实现方式 —— 它们都是用泛型 + 映射类型 + 条件类型拼出来的,
  没有任何魔法。看懂它们,你就能写自己的工具类型。

读一遍这个实现:T 是源类型,K extends keyof T 约束了 K 只能是 T 里真实存在的属性名(写错属性名会直接编译报错),然后 [P in K]: T[P] 遍历 K、把这些属性原样取出来组成新类型。

这一刻你应该有种"原来如此"的感觉:Partial / Required / Pick / Omit / Record / ReturnType 这些天天在用的工具类型,没有一个是"内置魔法",它们全是用这几个基础能力拼出来的。看懂了它们,你就能按同样的套路,写出贴合自己项目的类型工具。

实战建议:别为了炫技而炫技

高级类型很强,但也最容易被滥用。几条实战经验:

  • 优先用内置工具类型。能用 Partial / Pick / Omit 解决的,就别自己手写条件类型。
  • 高级类型的价值在"基础设施层"。写通用库、封装 API 层、做表单/状态管理这种"被很多地方复用"的代码,值得用复杂类型把约束做扎实;写普通业务组件,类型简单清晰就好。
  • 类型不是越复杂越好。一个嵌套五层条件类型、谁都看不懂的类型,维护成本极高。如果一个类型你三个月后自己都读不懂,它就是负债。
  • 善用 IDE 的类型悬浮提示。写复杂类型时,把鼠标悬在类型上看 TS 推断出来的结果,是调试类型最快的方式。

写在最后

TypeScript 的高级类型,本质是一套"在类型层面做编程"的工具:

  • 泛型 —— 让类型能像值一样被传递、复用;
  • 泛型约束(extends) —— 给类型参数划范围,表示"符合某个形状";
  • 条件类型(extends ? :)+ infer —— 类型层面的 if-else 和"提取";
  • 映射类型([K in keyof T]) —— 批量改造一个类型的所有属性。

这四样吃透,你看 TS 内置工具类型就像看源码一样清楚,也能写出真正为项目"兜底"的类型约束 —— 让 bug 在编译期就被拦下,而不是跑到线上才暴露。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

React 渲染机制深度解析:搞懂它,你的组件不再无故重渲染

2026-5-14 17:19:06

技术教程

C# async/await 深度解析:异步编程背后到底发生了什么

2026-5-14 17:19:07

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索