Skip to content
0x00 流畅的 TypeScript - 开篇<全集中·水之呼吸>

0x00 流畅的 TypeScript - 开篇<全集中·水之呼吸>

不同于 Java 等其他这种强类型语言,TypeScript 有一大优势在于它可以进行可类型推断,而且不是 rust 之类现代化语言的可省略类型标注,而是可控的类型推断。在这个大前提下,TypeScript 中的类型可以像函数一样被引用,计算,推导,变换为另一种类型,这是一个流动的强类型。

breath_of_water

结绳体现了时间的流动,将丝线汇聚在一起编织成型、扭曲、缠绕,有时又还原、断裂,再次连接,那就是时间。 --君の名は。

whats_your_name

写在前面

这个短篇既不是 TypeScript 基础指南, 也不是 TypeScript 体操分析;

可能你需要的是:

本文旨在从类型流动的角度出发,通过一系列简短的应用场景,讲解一些工具类型的使用场景,减少 TypeScript 类型学习过程中造成的强烈挫败感,回归 JavaScript 的流畅。 以及, 理解流动的哲学。

Be Water, My Friend. --李小龍

be_water

风起于青萍之末

让我们先从一个最简单场景开始, 针对用户(User) 的 CRUD 的基本类型定义;

把鼠标 🖱 放到 console.log 里面的变量上, 可以查看类型哦

type User = { id: number; name: string; tag: string; };
type List = (query: { id?: number; name?: string; tag?: string; }) => Resp<User[]>;
type Detail = (id?: number) => Resp<User>;
type Create = (neo: { name: string; tag?: string }) => Resp<number>;
type Remove = (id: number) => Resp<boolean>;
 
const CRUD: { list: List; detail: Detail; create: Create; remove: Remove; } = {
  list: (query) => console.log(query.id, query.name, query.tag),  // 🖱 
  detail: (id) => console.log(id), // 🖱
  create: (part) => console.log(part.name, part.tag), // 🖱
  remove: (id) => console.log(id), // 🖱
};

可以看到, 尽管我们已经重复使用 User 来减少类型定义的编写, 但 { id, name, tag } 这些字段仍然重复出现了很多次, 有些是毫无变化, 有些只是将必选变成了非必选; 除了编写费时费力之外, 特别的, 当我们字段发生变更的时候, 比如 id -> useId, 就需要对所有地方进行调整;

或者我们做一下改动

- type List = (query: { id?: number; name?: string; tag?: string; }) => Resp<User[]>;
+ type List = (query: Partial<User>) => Resp<User[]>;
- type Detail = (id?: number) => Resp<User>;
+ type Detail = (id?: User["id"]) => Resp<User>;
- type Create = (neo: { name: string; tag?: string }) => Resp<number>;
+ type Create = (neo: Omit<User, "id">) => Resp<User["id"]>;
- type Remove = (id: number) => Resp<boolean>;
+ type Remove = (id: User["id"]) => Resp<boolean>;

把代码变成这样

type User = { id: number; name: string; tag: string; };
type List = (query: Partial<User>) => Resp<User[]>;
type Detail = (id?: User["id"]) => Resp<User>;
type Create = (neo: Omit<User, "id">) => Resp<User["id"]>;
type Remove = (id: User["id"]) => Resp<boolean>;
 
const CRUD: { list: List; detail: Detail; create: Create; remove: Remove; } = {
  list: (query) => console.log(query.id, query.name, query.tag),  // 🖱 
  detail: (id) => console.log(id), // 🖱
  create: (part) => console.log(part.name, part.tag), // 🖱
  remove: (id) => console.log(id), // 🖱
};

可以看到 console.log 中的变量类型, 神秘的完全一致!, 很简单不是吗,我们只用了三个最基本的方法, 就让类型产生了流动!

在上述例子中, 我们用到了以下几个类型的引用方式

  • 引用 Type<"Index"> 将另一种类型上查找特定属性的类型
  • 转换 Partial<Type> 将类型的所有属性变为可选(Optional)
  • 转换 Omit<Type, Keys> 通过从 Type 中选择所有属性然后删除 Keys (并集) 来构造类型

除了减少字段的拼写之外, 我们还额外达成了

  1. type Create = (neo: Omit<User, "id">) => Resp<User["id"]>; 语义明确, 现在我们知道 Create 会返回的是用户的 id
  2. 联动修改, 将 User 上 id 属性从 number 调整为 string, 只需要修改一处代码!
  3. 改动警告, 将 User 上的 id 属性名字调整为 userId, 我们将会在所有需要修改的地方看到报错! 就像这样
type User = { userId: number; name: string; tag: string; };
type List = (query: Partial<User>) => Resp<User[]>;
type Detail = (id?: User["id"]) => Resp<User>;
Property 'id' does not exist on type 'User'.
type Create = (neo: Omit<User, "id">) => Resp<User["id"]>;
Property 'id' does not exist on type 'User'.
type Remove = (id: User["id"]) => Resp<boolean>;
Property 'id' does not exist on type 'User'.
const CRUD: { list: List; detail: Detail; create: Create; remove: Remove; } = { list: (query) => console.log(query.id, query.name, query.tag), // 🖱
Property 'id' does not exist on type 'Partial<User>'.
detail: (id) => console.log(id), // 🖱 create: (part) => console.log(part.name, part.tag), // 🖱 remove: (id) => console.log(id), // 🖱 };

小结

这个简单的例子应该已经能理解了 TypeScript 类型流动的意义, 以及在实际的场景中的实用价值, 后续我们将继续结合例子, 讲述一些常用的特性.

祝 Happy Typing 🎉