本文首发于个人博客,转载请注明出处:http://kohpoll.github.io/blog...
日常咱们编写 TypeScript 时,主要会使用类型注解(给变量、函数等加上类型约束),这能够加强代码可读性、避免低级 bug。实际上 TypeScript 的类型系统设计的很是强大,强大到能够单独做为一门编程语言。本文是本身学习 TypeScript 类型编程的一个总结,但愿对你有帮助。html
本文不会对 TypeScript 的基础语法和使用进行说明,你能够参考互联网上提供的优秀资料:git
参考 SCIP 中对于编程语言的描述。一门编程语言应该提供如下机制:github
下面咱们将以这三个方面为线索来探索 TypeScript 的类型编程。typescript
咱们首先来看看类型编程中,定义“变量”的方式:编程
// string、number、boolean 的值能够做为类型使用,称为 literal type type LiteralS = 'x'; type LiteralN = 9; type LiteralB = true; // 基础类型 type S = string; // 函数 type F = (flag: boolean) => void; // 对象 type O = { x: number; y: number; }; // tuple type T = [string, number];
这里稍微补充下 interface 和 type 的区别。数组
最主要的区别就是 type 能够进行“类型编程”,interface 不行。app
interface 能定义的类型比较局限,就是 object/function/class/indexable:编程语言
// object interface Point { x: number; y: number; } const p: Point = { x: 1, y: 2 }; // function interface Add { (a: number, b: number): number; } const add: Add = (x, y) => x + y; // class interface 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(); // indexable interface 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 = x type T = 1 | 2 | never; // 1 | 2 type Animal = { name: string }; type Flyable = { fly(): void }; type FlyableAnimal = Animal & Flyable; // { name: string, fly(): void }
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]
看起来是否是和编程语言里面的函数很类似?这些“函数”的输入(参数)是类型,通过“运算”后,输出是“类型”。接着咱们来看看在“函数体”(也就是等号右边)里面除了一些基本操做外,还能够作些其余什么骚操做。
将已有类型转换为一个新的类型,相似 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' }
条件类型能够理解为“三元运算”,T extends U ? X : Y,extends 能够类比为“相等”。
// 只保留string type 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,能够取得对象某个属性值的类型 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 infer type 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] // 更多其余的 infer type 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...