原文:The TypeScript Types That Terrified Me --- Until I Learned These 3 Rules
作者:Steve Sewell
我曾经对 TypeScript 的某些高级类型充满了敬畏(甚至是恐惧)。infer
、条件类型(Conditional Types)、映射类型(Mapped Types)......这些东西看起来就像是黑魔法,强大,但又深不可测。
我尝试过去阅读官方文档,但那些密密麻麻的理论和抽象的例子,常常让我看得云里雾里,感觉自己智商受到了挑战。我能看懂每个单词,但连在一起就不知道是啥意思了。
直到有一天,我顿悟了。我发现,理解这些"恐怖"类型的关键,并不在于死记硬背那些复杂的语法,而在于掌握它们背后的一些核心规则和模式。就像学武功要先懂心法一样。
今天,我就把我的"心法"------这 3 条黄金规则------分享给你,希望能帮你揭开这些高级类型的神秘面纱,让你也能轻松驾驭它们。
规则一:把类型当成"变量",用条件来"编程"
我们先从条件类型说起。它的语法是这样的:
typescript
SomeType extends OtherType ? TrueType : FalseType;
是不是看起来很像 JavaScript 里的三元运算符?没错,它的思想是完全一样的!
SomeType
就是你要检查的"变量"。extends
就是你的"判断条件"。? TrueType : FalseType
就是你的"if/else"分支。
核心思想: 条件类型让你的类型定义"动"了起来,你可以根据一个类型是否满足某个条件,来返回不同的类型。
举个栗子,我们想写一个类型,如果传入的是 string
,就返回 true
,否则返回 false
。
typescript
type IsString<T> = T extends string ? true : false;
let a: IsString<'hello'>; // a 的类型是 true
let b: IsString<123>; // b 的类型是 false
看,是不是很简单?你就在用类型来"编程"!
规则二:infer
不是推断,而是"声明一个新变量"
infer
关键字绝对是很多人的噩梦。官方文档说它是"推断",但这个词太抽象了。我更喜欢把它理解为**:在 extends
条件语句中,声明一个临时的、局部的类型"变量"**。
核心思想 : infer
就像一个占位符,它会捕获在类型匹配过程中,那个位置上实际的类型,然后你就可以在 true
分支里使用这个被捕获的类型了。
让我们来看一个经典的例子:获取一个函数类型的返回类型。
typescript
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
我们来拆解一下这个"咒语":
T extends (...args: any[]) => infer R
:这是我们的判断条件。我们在问:"T
是不是一个函数类型?"infer R
:这就是魔法发生的地方!我们在这里"声明"了一个新的类型变量R
。如果T
真的是一个函数,那么R
就会被赋值为这个函数的返回类型。? R : never
:如果T
是函数,我们就返回我们刚刚捕获到的返回类型R
。如果不是,就返回never
(一个表示"永不存在"的类型)。
typescript
type MyFunc = () => string;
type MyString = GetReturnType<MyFunc>; // MyString 的类型是 string
type NotAFunc = number;
type NeverHappens = GetReturnType<NotAFunc>; // NeverHappens 的类型是 never
所以,别再把 infer
想成什么高深的"推断"了,就把它当成你在 if
语句里声明的一个局部变量,是不是一下子就清晰了?
规则三:映射类型就是类型的"for...in"循环
映射类型(Mapped Types)允许你基于一个已有的类型,来创建出一个新的类型。它的语法可能会让你有点晕:
typescript
type MappedType<T> = {
[P in keyof T]: SomeNewType;
};
别怕,我们还是用 JavaScript 的思想来理解它。这玩意儿,本质上就是一个针对类型的 for...in
循环。
keyof T
:这会获取类型T
所有的键(keys),组成一个联合类型。就像Object.keys()
。[P in keyof T]
:这就是for (const P in Object.keys(T))
。它会遍历T
的每一个键。: SomeNewType
:在循环体内部,你可以对每个属性的类型进行转换,定义新的类型。
核心思想: 映射类型让你能够批量地、动态地修改一个对象类型中所有属性的类型。
举个栗子,我们想把一个类型的所有属性都变成只读的。TypeScript 内置的 Readonly<T>
就是用映射类型实现的:
typescript
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
interface User {
name: string;
age: number;
}
type ReadonlyUser = Readonly<User>;
// ReadonlyUser 的类型是:
// {
// readonly name: string;
// readonly age: number;
// }
我们来分析一下:
[P in keyof User]
:遍历User
的键,也就是'name'
和'age'
。readonly
:给每个属性加上readonly
修饰符。: T[P]
:属性的类型保持不变(T[P]
就是获取原类型T
中P
属性的类型)。
通过这个"循环",我们轻松地创建了一个所有属性都变成只读的新类型。
总结:用编程思维去理解类型
现在,我们再回过头来看这些"恐怖"的类型:
- 条件类型 :就是类型的
if/else
。 infer
:就是在if
语句里声明一个局部变量。- 映射类型 :就是类型的
for...in
循环。
你看,一旦我们把它们和熟悉的编程概念联系起来,它们就不再那么可怕了。它们只是工具,是 TypeScript 赋予我们用来操作和转换类型的强大武器。
下次当你再遇到这些高级类型时,不要慌。深呼吸,然后用这三条规则去"翻译"它们。你会发现,你也能成为驾驭 TypeScript 类型的"魔法师"。