-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Exploring TypeScript Type Annotations - Type Programming
作者: zhilidali
欢迎来到 《探索 TypeScript 类型注解》 系列教程。
上一篇介绍了 TS 的高级类型。
本篇将前面的知识点融会贯通,将对类型的探索提升一个层次:从类型层面进行编程。
目录
类型编程
首先,我们回顾一下前几节对类型的探索。
- 通过数据类型,我们了解到 TS 支持的数据类型。
- 支持 JS 数据类型。
- 新增一些数据类型。
- 通过自定义类型,我们了解到如何声明或命名一个类型。
- 接口,
interface
来声明各种复杂数据类型。 - 类型别名,
type
对类型进行命名。 - 泛型,
<T>
如同 JS 中函数将类型作为参数传递。
- 接口,
- 通过类型检查,我们了解到类型与 JS 之间的各种交互。
- 类型推断,通过 JS 的值推断出相应的类型。
- 类型断言,直接对 JS 中的值指定一个类型。
- 类型兼容,由类型之间的关系反映出 JS 值之间的关系。
- 类型保护,通过 JS 中的操作符来反映出精确的类型。
- 通过高级类型,我们了解到 TS 提供的各种类型操作符。
- 交叉类型,使用类型操作符
&
- 联合类型,使用类型操作符
|
- 索引类型,使用类型操作符
keyof
,T[K]
- 映射类型,使用类型操作符
in
- 条件类型,使用类型操作符
T extends U ? X : Y
infer
- 另外还有用于类型保护的类型谓词中的
is
操作符以及上面所介绍的typeof
- 交叉类型,使用类型操作符
根据 TS 提供的 数据类型 以及声明的自定义类型,结合高级类型中的 操作符 可以对类型进行各种运算 (高级类型本质上就是各种操作符表达式)。
再加上具有函数功能的 泛型,可以对类型的运算进行封装、复用、组合。要知道函数是 JS 中最强大的武器,谁说“类”来着,算了,好累,我还要(搬砖)拯救世界。还要知道,TS 没有采用传统面向对象语言使用的名义类型,而是基于偏向于函数式编程的结构类型,(JS 是多范式编程语言)。
到此为止,我们已经具备了对类型进行编程的各种工具 (程序 = 数据结构 + 算法),接下来各位童鞋就可以发挥无穷的智慧了。
童鞋请留步!俗话说吃人嘴短,拿人手软,请先让巨硬(微软大大)炫个富。下面介绍 TypeScript 官方标准库中封装的实用工具类型。
实用工具类型
TypeScript 提供的实用工具类型用来实现常见的类型转换,这些类型工具函数是全局可见的。
Extract,Exclude,NonNullable
Extract<T, U>
:从T
中提取可以赋值给U
的类型Exclude<T, U>
:从T
中排除可以赋值给U
的类型NonNullable<T>
:从T
中排除null
和undefined
使用示例
type foo = Extract<number | string, string>; // string
type bar = Exclude<number | string, string>; // number
type baz = NonNullable<number | string | null | undefined>; // string | number
具体实现
// 主要使用条件类型 `T extends U ? X : Y` 实现
type Extract<T, U> = T extends U ? T : never;
type Exclude<T, U> = T extends U ? never : T;
type NonNullable<T> = T extends null | undefined ? never : T;
Partial, Require, Readonly
Partial<T>
:将T
中的所有属性设置为可选Require<T>
:将T
中的所有属性设置为必选Readonly<T>
:将T
中的所有属性设置为只读
使用示例
interface Type { a: number, b?: string };
let foo: Partial<Type> = { b: 'b' };
let bar: Required<Type> = { a: 1 }; // Error
let baz: Readonly<Type> = { a: 1 };
baz.a = 2; // Error
具体实现
// 主要使用映射类型 `[K in T]: Type` 及索引类型 `keyof T`、`T[P]` 实现
type Partial<T> = { [P in keyof T]?: T[P] };
type Require<T> = { [P in keyof T]-?: T[P] }; // 注意这里的 `-?`
type Readonly<T> = { readonly [P in keyof T]: T[p] };
TypeScript 标准库中提供了许多实用的工具类型,而且随着 TypeScript 不断更新迭代,会有更多的实用工具类型加入到标准库中,此处不在重复介绍(提示:实用工具很实用),详情请移步官方手册,手册中给出了详细的使用示例。对于这些工具类型的具体实现,请移步官方仓库的 lib。
typeof
在 TS 中,还可以使用 typeof
来获取变量的类型。
let foo: number = 3;
type bar = typeof foo; // 相当于 type bar = number
extends
前面的章节中多处使用了 extends
关键字。如下
原生 JS 中类的继承
class A { a: number }
class B extends A { b: string } // B 继承 A
let a: A = new A();
let b: B = new B();
a = b; // Ok, A = B 少兼容多,子类兼容超类
接口继承
interface A { a: number }
interface B extends A { b: string }
let a: A = { a: 1 };
let b: B = { b: 'b', a: 1 };
a = b; // Ok, A = B
泛型约束
interface A { a: number }
let foo: <B extends A>(arg: B) => void;
foo({ a: 1, b: 2});
条件类型
interface A { a: number }
interface B { a: number, b: string }
type E = B extends A ? true : false;
// type E = true
汇总如下
- 类的继承:
class SubClass extends SupClass
- 接口继承:
interface SubType extends SupType {}
- 泛型约束:
<T extends U>
- 条件类型:
T extends U ? X : Y
以上均有共同的形式 Sub extends Sup
- 从
extends
关键字的语义:它们之间属于继承关系,即子类(型)继承超类(型)。 - 从类型兼容性角度:超类型兼容子类型,即子类型可以赋给超类型。
- 从功能上:
- 类和接口中的
extends
用来定义,可有多个超类Sup
,中间用,
分割。 - 泛型约束和条件类型中的
extends
用来检测兼容性,即Sup
是否兼容Sub
- 类和接口中的
类型与集合
既然是编程,下面从数学的角度来简单粗略地描述 TS 类型系统,(读者可略过,想深入的童鞋可移步:https://zhuanlan.zhihu.com/p/38081852)。
TS 中的类型好比数学中的集合,类型是具有某种特定性质的 JS 值的集合。
比如 number
类型对应 JS 中所有数值的集合。
类型集合的分类
any
类型对应为 全集。never
类型对应为 空集。- 联合类型是类型集合之间的 并集。
- 交叉类型是类型集合之间的 交集。
结语
本篇主要是对类型进行编程的能力进行了梳理。
协议
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。
《探索 TypeScript 类型注解》
- To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel.
参考链接
Handbook: https://github.com/microsoft/TypeScript-Handbook
深入 TypeScript 的类型系统: https://zhuanlan.zhihu.com/p/38081852
Activity
leohxj commentedon Jan 8, 2020
这个系列写的很好。
[-]探索 TypeScript 类型注解 - 实用工具类型[/-][+]探索 TypeScript 类型注解 - 类型编程[/+]