0x00 流畅的 TypeScript - 开篇<全集中·水之呼吸>
不同于 Java 等其他这种强类型语言,TypeScript 有一大优势在于它可以进行可类型推断,而且不是 rust 之类现代化语言的可省略类型标注,而是可控的类型推断。在这个大前提下,TypeScript 中的类型可以像函数一样被引用,计算,推导,变换为另一种类型,这是一个流动的强类型。
结绳体现了时间的流动,将丝线汇聚在一起编织成型、扭曲、缠绕,有时又还原、断裂,再次连接,那就是时间。 --君の名は。
写在前面
这个短篇既不是 TypeScript 基础指南, 也不是 TypeScript 体操分析;
可能你需要的是:
本文旨在从类型流动的角度出发,通过一系列简短的应用场景,讲解一些工具类型的使用场景,减少 TypeScript 类型学习过程中造成的强烈挫败感,回归 JavaScript 的流畅。 以及, 理解流动的哲学。
Be Water, My Friend. --李小龍
风起于青萍之末
让我们先从一个最简单场景开始, 针对用户(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 (并集) 来构造类型
除了减少字段的拼写之外, 我们还额外达成了
type Create = (neo: Omit<User, "id">) => Resp<User["id"]>;
语义明确, 现在我们知道 Create 会返回的是用户的 id- 联动修改, 将 User 上 id 属性从 number 调整为
string
, 只需要修改一处代码! - 改动警告, 将 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>;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), // 🖱
};
小结
这个简单的例子应该已经能理解了 TypeScript 类型流动的意义, 以及在实际的场景中的实用价值, 后续我们将继续结合例子, 讲述一些常用的特性.
祝 Happy Typing 🎉