本文首发于我的博客,转载请注明出处:http://kohpoll.github.io/blog...

平时咱们编写 TypeScript 时,次要会应用类型注解(给变量、函数等加上类型束缚),这能够加强代码可读性、防止低级 bug。实际上 TypeScript 的类型零碎设计的十分弱小,弱小到能够独自作为一门编程语言。本文是本人学习 TypeScript 类型编程的一个总结,心愿对你有帮忙。

开始之前

本文不会对 TypeScript 的根底语法和应用进行阐明,你能够参考互联网上提供的优良材料:

  • https://www.typescriptlang.or...
  • https://basarat.gitbook.io/ty...

启程

参考 SCIP 中对于编程语言的形容。一门编程语言应该提供以下机制:

  • 根本表达式。用来示意语言所关怀的最简略的个体。
  • 组合的办法。从简略的个体登程结构复合的对象。
  • 形象的办法。能将复合对象封装作为独立单元去应用。

上面咱们将以这三个方面为线索来摸索 TypeScript 的类型编程。

根本表达式

咱们首先来看看类型编程中,定义“变量”的形式:

// string、number、boolean 的值能够作为类型应用,称为 literal typetype LiteralS = 'x';type LiteralN = 9;type LiteralB = true;// 根底类型type S = string;// 函数type F = (flag: boolean) => void;// 对象type O = { x: number; y: number; };// tupletype T = [string, number];

这里略微补充下 interface 和 type 的区别。

最次要的区别就是 type 能够进行“类型编程”,interface 不行。

interface 能定义的类型比拟局限,就是 object/function/class/indexable:

// objectinterface Point {x: number;y: number;}const p: Point = { x: 1, y: 2 };// functioninterface Add {(a: number, b: number): number;}const add: Add = (x, y) => x + y;// classinterface ClockConstructor {new (hour: number, minute: number): ClockInterface;}interface ClockInterface {tick(): void;}const Clock: ClockConstructor = class C implements ClockInterface {constructor(hour: number, minute: number) { return this; }tick() {}}const c = new Clock(1, 2);c.tick();// indexableinterface StringArray {[index: number]: string;}interface NumberObject {[key: string]: number;}const s: StringArray = ['a', 'b'];const o: NumberObject = { a: 1, b: 2 };

interface 能够被从新“关上”,同名 interface 会主动聚合,非常适合做 polyfill。比方,咱们想要在 window 上扩大一些本来不存在的属性:

interface Window {g_config: {locale: 'zh_CN' | 'en_US';};}

组合的办法

有了根本表达式,咱们来看组合的办法。

| 和 & 操作符

& 示意必须同时满足多个契约,| 示意满足任意一个契约即可。

type Size = 'large' | 'normal' | 'small';// never 能够了解为 | 运算的“幺元”,即:x | never = xtype T = 1 | 2 | never; // 1 | 2type Animal = { name: string };type Flyable = { fly(): void };type FlyableAnimal = Animal & Flyable; // { name: string, fly(): void }

keyof 操作符

interface Sizes {large: string;normal: string;small: string;x: number;}// 获取对象的属性值type Size = keyof Sizes; // 'large' | 'normal' | 'small' | 'x'// 反向获取对象属性的类型type SizeValue = Sizes[keyof Sizes]; // string | number// keyof any 能够了解为能作为“索引”的类型type K = keyof any; // string | number | symbol

形象的办法

形象的办法实际上指的就是“函数”。咱们来看看类型编程中,“函数”该怎么定义。

// “定义”type Id<T> = T;// “调用”type A = Id<'a'>; // 'a'// “参数”束缚及默认值type MakePair<T extends string, U extends number = 1> = [T, U];type P1 = MakePair<'a', 2>; // ['a', 2]type P2 = MakePair<'x'>; // ['x', 1]

看起来是不是和编程语言外面的函数很类似?这些“函数”的输出(参数)是类型,通过“运算”后,输入是“类型”。接着咱们来看看在“函数体”(也就是等号左边)外面除了一些基本操作外,还能够做些其余什么骚操作。

“映射”操作(mapped)

将已有类型转换为一个新的类型,相似 map。返回的新类型个别是对象。

type MakeRecord<T extends keyof any, V> = {[k in T]: V};type R1 = MakeRecord<1, number>; // { 1: number }type R2 = MakeRecord<'a' | 1, string>; // { a: string, 1: string }type TupleToObject<T extends readonly any[]> = {[k in T[number]]: k};type O = TupleToObject<['a', 'b', 'c']>; // { a: 'a', b: 'b', c: 'c' }

条件——extends

条件类型能够了解为“三元运算”,T extends U ? X : Y,extends 能够类比为“相等”。

// 只保留stringtype OnlyString<T> = T extends string ? T : never;type S = OnlyString<1 | 2 | true | 'a' | 'b'>; // 'a' | 'b'// 这里的计算过程大抵是:// 1 | 2 | true | 'a' | 'b' -> never | never | never | 'a' | 'b'// 依据 x | never = x,最终失去 'a' | 'b'// 取得对象的函数类型的属性key值type FunctionPropertyNames<T> = {[k in keyof T]: T[k] extends Function ? k : never}[keyof T];interface D {id: number;add(id: number): void;remove(id: number): void;}type P = FunctionPropertyNames<D>; // 'add' | 'remove'// 这里的计算过程大抵是:// 将 interface 开展:// {// id: D['id'] extends Function ? 'id' : never, //-> false// add: D['add'] extends Function ? 'add' : never, //-> true// remove: D['remove'] extends Function ? 'remove' : never //-> true// }['id' | 'add' | 'remove']// 计算条件类型:// {// id: never,//. add: 'add',// remove: 'remove'// }['id' | 'add' | 'remove']// 依据索引取值:// never | 'add' | 'remove'// 依据 never | x = x,最终失去:'add' | 'remove'

“析构“——infer

infer 能够了解为一种“放大镜”机制,能够“捕捉”到被“嵌”在各种简单构造里的类型信息。

// 对象 infer,能够获得对象某个属性值的类型type ObjectInfer<O> = O extends { x: infer T } ? T : never;type T1 = ObjectInfer<{x: number}>; // number// 数组 infer,能够获得数组元素的类型type ArrayInfer<A> = A extends (infer U)[] ? U : never;const arr = [1, 'a', true];type T2 = ArrayInfer<typeof arr>; // number | string | boolean// tuple infertype TupleInfer<T> = T extends [infer A, ...(infer B)[]] ? [A, B] : never;type T3 = TupleInfer<[string, number, boolean]>; // [string, number | boolean]// 函数 infer,能够获得函数的参数和返回值类型type FunctionInfer<F> = F extends (...args: infer A) => infer R ? [A, R] : never;type T4 = FunctionInfer<(a: number, b: string) => boolean>; // [[a: number, b: string], boolean]// 更多其余的 infertype PInfer<P> = P extends Promise<infer G> ? G : never;const p = new Promise<number>(() => {});type T5 = PInfer<typeof p>; // number

能够发现下面的例子须要应用 infer,是因为咱们在“定义”时不晓得具体的类型,须要在“调用”时做“推断”。infer 帮咱们标注了待推断的类型,最终计算出理论的类型。

嵌套&递归

在“函数体”中,咱们其实能够再“调用函数“,造成一种嵌套和递归的机制。

// 取函数第一个参数的类型type Params<F> = F extends (...args: infer A) => any ? A : never;type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;type FirstParam = Head<Params<(name: string, age: number) => void>>; // string// 递归定义type List<T> = {value: T,next: List<T>} | null;

序幕

文章写到这里根本就完结了,这篇文章的内容可能在平时的开发中会比拟少遇到,然而对于补全本人的 TypeScript 体系、开阔视野还是有所帮忙的。如果想更多的来些实战演练,举荐看看这个:https://github.com/type-chall...