[Day07] TS:什麽是 Utility Types?

Utility Type

上面这个是今天会提到的内容,如果你已经可以轻松看懂,欢迎直接左转去看我同事 Andy 「前端工程师学习 DevOps 之路」的精彩文章!。

昨天我们整合了过去所学的知识写了一个函式,後面我们会提到更多 TypeScript 中用来建立 Utility Types 所需的知识,但今天让我们继续熟悉前几天学到的内容,并试着建立一些常用的 Utility Types 吧!

前几天学到的:

  • 泛型(generics)的使用
  • 使用 extends 限制泛型
  • keyof 的使用
  • Indexed Access Types 的使用

Utility Types 是什麽

一般写程序时,或多或少会写过一些 utility function,它们就像小工具,可以接受 input 然後做了某些处理後回传 output,举例来说,以阿拉伯数字作为 input,接着以中文的数字作为 output;或者以字串作为 input,根据某些字元拆成阵列後作为 output。不管功能是什麽,这种「小工具」类,有 input 和 output 的函式,就可以称作 utility。

除了函式之外,在 TypeScript 中,也有不少处理型别的小工具可以使用,和前面提到的 utility functions 最大的不同在於,代入 Utility Types 的 input 会是 TypeScript 的「型别」,而不是一般的 JavaScript value,也就是说,Utility Types 会以「型别」作为 input,并且以另一个「型别」作为 output,也就是说,Utility Types 就像函式一样可以带入 input 得到 output,透过 Utility Types 将可以「根据一个型别,来建立出另一个型别」

Utility Types 有时也称作 Type Function,TypeScript 本身就有许多内建的 Utility Types,像是 PartialRequiredRecord、...等等。这里我们先来看一下比较基本的,先对 Utility Types 有一点感觉,等後面学到更多知识後,再来看其他更进阶的。

建立一些简单的 Utility Types

今天这里所提到的一些 Utility Types 读者们可能会觉得有点鸡肋,似乎不需要建立 Utility Types 就可以达到一样的功能,不过相信我,等到後面掌握更多 TypeScript 的知识後,读者将会有一种看到不同世界的感觉。

OrNull

先来看一下官方提到的 OrNull 这个 Utility Types,它的写法是这样:

// Utility Type
type OrNull<Type> = Type | null;

看起来非常单纯,使用上可以像这样:

type Manufacture = 'Apple' | 'Google' | 'Samsung' | 'Sony';

// 使用 OrNull 这个 Utility Type 产生新的型别 ManufactureOrNull
type ManufactureOrNull = OrNull<Manufacture>;

const manufacture: ManufactureOrNull = 'Apple';

你会看到在使用 OrNull 这个 Utility Types 是,就像呼叫一个 function 一样,我们把 Manufacture 当成参数透过 <> 传入 OrNull 中,而它会回传一个新的型别给我们。

ManufactureOrNull 实际上的值其实也就是 'Apple' | 'Google' | 'Samsung' | 'Sony' | null,虽然 OrNull 这个 Utility Types 看起来很鸡肋,但有时其实还蛮好用的,因为它会让我们的程序看起来比较乾净一些:

function getManufacture(manufacture: Manufacture | null) {
  /*...*/
}
function getManufacture(manufacture: OrNull<Manufacture>) {
  /*...*/
}

举例来说,上面这两个 function 对於参数 manufacture 的型别定义虽然一样,但後者个人看起来就更工整了一些,而不会有一种 null 是多加出来的感觉。

OneOrMany

再来一样是官方有说明到的 OneOrMany,写法是这样:

// Utility Type
type OneOrMany<Type> = Type | Type[];

使用上像这样:

type Manufacture = 'Apple' | 'Google' | 'Samsung' | 'Sony';

// 使用 OneOrMany 这个 Utility Type 产生新的型别 ManufactureOrManufactures
type ManufactureOrManufactures = OneOrMany<Manufacture>;

const manufactures: ManufactureOrManufactures = ['Apple', 'Google'];

这里一样可以看到,我们把 Manufacture 当成参数一样从 <> 传进 OneOrMany 这个 Utility Type 中,并得到新的 ManufactureOrManufactures 型别,它的值只要满足 Manufacture 或者是 Manufacture[] 都可以。

OneOrManyOrNull

Utility Types 就像函式一样,所以也可以一个 Utility Type 包着另一个 Utility Type,例如:

// Utility Type
type OneOrManyOrNull<Type> = OrNull<OneOrMany<Type>>;

这里 OneOrManyOrNull 是一个 Utility Type,而它是透过 OrNullOneOrMany 同时组合出来的,使用的方式一样:

type Manufacture = 'Apple' | 'Google' | 'Samsung' | 'Sony';

// 使用 OneOrManyOrNull 这个 Utility Type 产生新的型别 OneOrManyOrNullOfManufacture
type OneOrManyOrNullOfManufacture = OneOrManyOrNull<Manufacture>;

const manufacturesData: OneOrManyOrNullOfManufacture = null;

这里一样可以把 Manufacture 当成参数一样传入 OneOrManyOrNull 这个 Utility Type 中,现在 OneOrManyOrNullOfManufacture 指的就会是 Manufacture | Manufacture[] | null

物件型别的 Utility Types

从上面的几个例子中,相信读者应该可以感受到 Utility Types 能够像 function 一样,输入 input 并取得 output 的感觉,只是 input 和 output 都需要是 TypeScript 的型别。

让我们回顾一下昨天的 getObjValue 这个函式,实际上如果不管函式本身,针对物件型别也是可以建立出几个不同的 Utility Types。

这里我们先建立一个物件型别作为後面的范例:

type Manufacture = 'Apple' | 'Google' | 'Samsung' | 'Sony';
type Product = {
  name: string;
  price: number;
  manufacturer: Manufacture;
  isLaunched: boolean;
};

Keys

建立一个 Utility Type 来取出所有物件型别的 keys 且只要 string

// Utility Type
type Keys<T> = keyof T & string;

使用的方式:

type KeysOfProduct = Keys<Product>; // "name" | "price" | "manufacturer" | "isLaunched"

这时候 KeysOfProduct 的结果会是 "name" | "price" | "manufacturer" | "isLaunched"

读者可以注意到,Product 这个物件型别一样可以被当成参数一样传入 Keys 这个 Utility Type 的 <> 内。

Values

Values 这个 Utility Type 则是可以取出物件型别的所有属性值(型别),读者们可以根据昨天的练习试着自己写写看:

// Utility Type
type Values<T> = T[keyof T];

使用的方式一样可以把物件型别 Product 当成参数带入:

type ValuesOfProduct = Values<Product>; //  string | number | boolean

最後会得到新的型别 ValuesOfProduct

PickObj

最後来写一个 Utility Type,它的作用是取出物件型别中的某个 key 的属性值(型别),读者们一样可以根据昨天的内容试着练习看看:

// Utility Type
type PickObj<T, U extends keyof T> = T[U];

这里一样要记得 U extends keyof T 的使用,如果没有限制泛型 U 一定要是物件型别 T 的 key,TypeScript 因为没办法确保能在 T 中找到 U 这个 key,将会报错:

Type Utilities

使用上则是可以同时带入两个型别参数:

type Price = PickObj<Product, 'price'>; // number

如此就可以取出物件型别中 key 为 price 的属性值的型别。

Utility Types 学习小技巧

今天提到的一些 Utility Types 多半还蛮直观的,甚至可能会觉得可有可无,重点是让读者先对於可以把 Type 当成 function 一样使用有些感觉,後面当我们学到 Conditional Types、Mapped Types 等等更进阶的用法後,你将会发现「Wow!原来型别还可以这样『玩』!」。

这里提供一些未来在学习 Utility Types 是,方便好用的小技巧:

  • 在学习 Utility Types 是,把抽象的东西用一些具体的值带入,可以帮助我们更好了解
  • 擅用 Type Alias,把 Utility Type 回传的结果保存成一个 Type,可以更清楚看到该 Type Utility 的作用。

范例程序码

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

参考资料


<<:  Day07 - Login to Ptt

>>:  ASP.NET MVC 从入门到放弃(Day17)-MVC控制器(Controller)介绍

Day 20 - OR、AND 的活用方式与短路取值

前言 今天来介绍我个人很常用的小撇步,关於 OR (||) 与 AND (&&),除...

安装 Debian 11 与呒虾米

前言 Debian 也在这几天释出第 11 版,开发代号 bullseye,是目前的稳定 (Stab...

Data layer implementation (2)

上一篇的 repository 还欠一个 mapper 把 EtaResponse 转成 EtaRe...

Tortoise SVN 环境建置

1.下载 https://tortoisesvn.net/downloads.html 2.安装 :...

Day-02 JavaScript资料型别(1)

历史背景 1990年代,是灰色缉毒犬PC防毒,回纹针干扰文书,Internet Explorer 还...