[Day08] TS:什麽!型别也能做条件判断?认识 Conditional Types

Conditional Types

上面这个是今天会提到的内容,如果你已经可以轻松看懂,欢迎直接左转去看我队友们的精彩文章!

昨天我们对於 Utility Types 有了更多认识,发现原来 Utility Types 可以像函式一样接受型别当作 input 然後回传一个新的型别当作 output。今天来看一下原来在 TypeScript 中,型别还可以做到「条件判断(Conditional Types)」!?

在 TypeScript 中,虽然没办法针对型别使用 if ... else 来做条件判断,但可以使用 JavaScript 中同样有的三元运算子(ternary operator)来达到条件判断的目的,也就是用 ?:

语法大概会像这样:

Conditional Types

这里读者可以留意到 X extends Y 就像是一个放在 if() 括号内的条件式,这里的 extends 就像先前在泛型时提到过的,要当作是「能够满足」的意思。X 能够满足 Y,更确切来说是指「X 是 Y 的子集合(subset)」

在学习 Conditional Types 或 Utility Types 时,可以多利用 Type Alias 的方式,建立一个型别後,看看它最终会是什麽。以 Conditional Type 来说,可以用它来建立一个名为 NewType 型别:

type NewType = X extends Y ? true : false;

上面的意思翻成白话文就是「如果 X 是 Y 的子集合的话,则 NewType 就会是 true,否则的话 NewType 会是 false」。

关於 extends 的使用我喜欢用「集合」搭配图像的方式来思考,X extends Y 表示 Y 是比 X 更大的集合:

Conditional Types

如果你觉得还有点太过抽象,可以把抽象的东西用一些具体的值带入,可以帮助我们更好了解。

现在再把它更具体一点:

  • 定义 SomeType1,让它的值直接是个字串(String Literal Types)
  • 使用 conditional type 建立 NewType1
type SomeType1 = 'any-string'; // String Literal Types
type NewType1 = SomeType1 extends string ? true : false; // true

这时因为 SomeType1 是 String Literal Types,它是 string 的子集合,所以 NewType1 就会是 true。用图像来描述的话会像是这样:

Conditional Types

接着定义另一个 SomeType2,让它的值直接是个数字(Number Literal Types):

type SomeType2 = 0; // Number Literal Types
type NewType2 = SomeType2 extends string ? true : false; // false

这时候因为 SomeType2 是 Number Literal Types,并不是 string 的子集合,因此 NewType2 就会是 : 後的 false。用图像来表示的话会是这样:

Conditional Types

这就是 Conditional Types 的语法。Conditional Type 虽然看起来简单,但实际上可以做出很多不同的变化,我们会在後面几天一直看到它的身影。

在 TypeScript 中,可以直接使用「布林值」、「字串值」和「数值的值」作为型别,这种用法称作 Literal Types

实际范例

在了解 Conditional Types 的使用後,让我们来看官网提供的 Flatten 这个范例, Flatten 一样可以视为一个 Utility Type:

Conditional Types

让我们先来理解一下 T extends any[] 是什麽意思。

除了把抽象的东西用一些具体的值带入之外,在理解 Conditional Types 时,可以把 ?: 後的内容先替换成其他内容,方便理解它是跑到了那一个条件

这里先把 ? 後的值改成 true: 後的值改成 false,像是这样:

// 当我们不清楚「条件」是什麽意思是,可以修改 ? 和 : 後的内容
type Flatten<T> = T extends any[] ? true : false;

接着分别定义型别 ManufactureManufactures,然後把它带入 Flatten<> 中:

type Manufacture = 'Apple' | 'Google' | 'Samsung' | 'Sony';
type Manufactures = Manufacture[];

type FlattenManufacture = Flatten<Manufacture>; // false
type FlattenManufactures = Flatten<Manufactures>; // true

把滑鼠移到 FlattenManufactureFlattenManufactures 就可以看到回传的型别:

conditional types

你会发现如果带入的是 Manufacture ,因为 Manufacture 不是 any[] 的子集合,因此会进到 false

Conditional Types

相反地,如果带入的是 Manufactures,以为 Manufactures 本身就是 Manufacture[],可以算是 any[] 的子集合,进而得到 true

Conditional Types

搭配原本对於 TypeScript 的理解,应该就可以知道 T extends any[] 翻成白话文,指的就是「T 是否符合阵列型别」。

在理解 Condition 後,接着让我们把 Flatten 改回原本的样子:

type Flatten<T> = T extends any[] ? T[number] : T;

现在我们可以理解,当 T 不是阵列型别的子集合时,它会直接回传 : 後的 T,也就是什麽都不做直接回传原本的型别。但如果 T 是阵列型别的子集合,那 T[number] 是什麽意思呢?

同样的,我们可以带一个实际的型别来取代 T 帮助我们理解。我们把 T 用符合阵列型别的 Manufactures 带进去看看:

type Manufacture = 'Apple' | 'Google' | 'Samsung' | 'Sony';
type Manufactures = Manufacture[];

type ShowMeTheType = Manufactures[number];

接着把滑鼠移到 ShowMeTheType 上面:

Indexed Access Types

你会发现,因为 Manufactures 的型别是 Manufacture[],而使用 Manufactures[number] 的意思其实就是把阵列型别里的元素型别取出的意思(可以参考内文後方针对 Index Access Types 的补充)。

回到一开始的范例,可以知道当 T 是阵列型别的子集合时,会得到的是 T[number],也就是取出 T 这个阵列型别中元素的型别。所以说 Flatten 这个 Utility Type 的作用就是「把阵列型别中的元素型别摊平後回传,如果不是阵列元素就什麽都不要做」的意思。

使用上可以像这样:

type FlattenManufacture = Flatten<Manufacture>; // "Apple" | "Google" | "Samsung" | "Sony"
type FlattenManufactures = Flatten<Manufacture[]>; // "Apple" | "Google" | "Samsung" | "Sony"

type Example1 = Flatten<['a', true, 3]>; // true | 3 | "a"
type Example2 = Flatten<string[]>; // string
type Example3 = Flatten<'not array'>; // "not array"

补充:使用 Indexed Access Types 取得阵列型别中元素的型别

稍微补充一下,在 Day 05 的时候我们有提了 Indexed Access Types,当时提到的是针对物件型别来使用 Indexed Access Type 的话,可以取出物件型别中属性值的型别,但如是针对阵列型别的话,这可以使用 Indexed Access Types 来取出阵列型别中元素型别,因为阵列型别的 index 一定是 number 型别的缘故,所以只要使用 SomeArray[number] 就可以取出 SomeArray 这个阵列型别里面元素值的型别。

举例来说:

type StringArray = string[];
type StringArrayElement = StringArray[number]; // string

type NumberArray = number[];
type NumberArrayElement = NumberArray[number]; // number

当然也可以用实际数值的方式取出某 index 元素的型别:

type SomeArray = [string, boolean, number];
type Element0 = SomeArray[0]; // string
type Element1 = SomeArray[1]; // boolean
type Element2 = SomeArray[2]; // number
type Elements = SomeArray[number]; // string | boolean | number

Conditional Types 学习小技巧

  • 在理解 Conditional Types 时,可以把 ?: 後的内容先替换成其他内容,方便理解它是跑到了那一个条件。这招非常实用,特别是在未来你可以能会看到多重的 ? A : B ? C : D 时。
  • 所谓「X 满足 Y」的意思,确切来说指的是「X 是 Y 的子集合(subset)」。

范例程序码

https://tsplay.dev/wXkB8W @ TypeScript Playground

参考资料


<<:  Python - Scrabble Word Finder - Python 爬虫练习笔记

>>:  【Day 23】JavaScript 条件(三元)运算子

Day-6 Build a CPU

Build a CPU tags: IT铁人 抽象化设计 建构一台电脑时,他要能执行所有指定ISA的...

IT 铁人赛 k8s 入门30天 -- day28 k8s Service Catalog

前言 Service Catalog 介绍参考文件 https://kubernetes.io/do...

Now available the of Spotify Premium APK for all Android

Although YouTube Music hit the global market a few...

[day-5] 我与我的第一个程序,HelloWorld和背後的历史!

Hello World 的起源 通常我们在测试一个程序或是一个环境是否正常 会习惯使用 Hello ...

分散式链路追踪 - Jaeger

在 OpenTelemetry 中有提到 trace 的概念。而 Trace 由多个 Span 组成...