前言:在编写 typescript 应用的时候,有时候咱们会但愿复用或者构造一些特定结构的类型,这些类型只从 typescript 靠内建类型和 interface、class 比较难以表达,这时候咱们就须要用到类型推导, 而讨论类型推导, 则离不开泛型和推断(#infer), 本文咱们只讨论泛型html
上一篇typescript
从形式上看, typescript 中的泛型如同大多数语言(不包括还没有实现的 Go :P)里的泛型:数组
// one constrain for array-like object
interface ArrayLike<T> {
readonly length: number;
readonly [n: number]: T;
}复制代码
上面的 ArrayLike
表达了类数组结构, 它表明的对象的特征是:bash
length
字段;array-like 对象是 Javascript 中很古老的对象, 如声明为 function (...) {...}
格式的函数, 其内部的 arguments 变量就是典型的 array-like 对象.函数
泛型每每能够用在这些场景:学习
接下来咱们依次说明, 在这些场景中, 类型推导如何发挥它的威力.ui
在利用泛型作类型推导时, 切记:es5
第 2 点可能有点难以理解, 咱们先略过, 在下一篇, 咱们会明白这句话的含义.spa
在上一篇层提到, 咱们能够经过 keyof
提取一个 interface 的全部键名, 当引入泛型后, keyof 还能够作更有趣的事情:code
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};复制代码
Partial
的做用是: 对 T(T 须要是可被当作 interface 的类型), 求其全部的键名, 并依赖键名, 获得一个结构彻底相同, 但其全部键均可选 的新 interface.
好比, 对与 Form, UninitForm 能够是它的全键可选项版本:
interface Form {
name: string
age: number
sex: 'male' | 'female' | 'other'
}
type UninitForm = Partial<Form>复制代码
则 UninitForm 等价于:
{
name?: string;
age?: number;
sex?: "male" | "female" | "other";
}复制代码
相反, 若是你已经有了 UninitForm, 则你能够得靠 Required
到它的全键必需化版本:
/**
* Make all properties in T required
*/
type Required<T> = {
[P in keyof T]-?: T[P];
};复制代码
interface UninitForm {
name?: string;
age?: number;
sex?: "male" | "female" | "other";
}
type AllKeyRequriedForm = Required<UninitForm>复制代码
结合 readonly, 咱们能够把一个 interface 里全部的键转化为只读
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};复制代码
对于 js 而言, extends
是扩展类的保留字; 而在 typescript 中, 当 extends 出现的以下的场景时, 它意味着类型推导:
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;复制代码
形如 T extends [DEP] ? [RESULT1] : [RESULT2]
的表达式, 是 typescript 中的一种类型推导式, 它的规则是:
若泛型 T 必须知足 [DEP] 的约束(即 T extends [DEP]
为 true
), 则表达式结果为 [RESULT1]; 反之表达式结果为 [RESULT2]:
T extends [DEP]
为 true
, 反之为 false
T extends [DEP]
为 true
, 反之为 false
T extends [DEP]
恒为 true
按照这些规则, 咱们来分析一下这个 Exclude
.
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;复制代码
分析可知:
Exclude
中有两个必要的泛型标记 T
和 U
(由于它们都未提供默认泛型)咱们来应用一下 Exclude
type NoOne = Exclude<1 | 2 | 3, 1> // NoOne = 2 | 3复制代码
另外, 为了说明上面的第 4 点, 咱们能够写一个毫无用处的 NotRealExclude
;
type NotRealExclude<T, U> = T extends U ? U : T;
type Orig = NotRealExclude<1 | 2 | 3, 1> // Orig = 1 | 2 | 3复制代码
因为 NotRealExclude
在 T 不符合 U 的时候返回 U, 而在 T 符合 U 的时候又返回 T, 最终的结果是: 组成 T 的全部类型又被从新组装了起来.
在了解了 extends
的基本用法后, 咱们来看更多的例子:
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};复制代码
正如咱们经常使用的 pick 函数 能够提取对象中特定的键值对, Pick
也能够提取 T 中特定的键值定义.
interface Person {
name: string
age: number
sex?: "male" | "female" | "other";
}
/**
* equivalent to { name: string }
*/
type SimplePersonInfo = Pick<Person, 'name'>复制代码
既然能够 Pick
, 那也能够 Omit
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;复制代码
/**
* equivalent to { name: string, age: number }
*/
type SimplePersonInfo = Omit<Person, 'sex'>复制代码
咱们知道, 在对象的索引中, in
关键字能够用于从联合类型中提取类型, 做为 interface 的键名, 好比:
type Ks = 'a' | 'b' | 'c'
type KObject = {
[P in Ks]: any
}复制代码
则 KObject 能够包含 'a'
, 'b'
, 'c'
三种类型的键名.
结合 extends, 咱们能够轻松地从一个 interface 构建具备一样类型的值的字典, 这就是 Record
:
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};复制代码
// construct one family with 3 keys: parent, mom, child
type Family = Record<'parent' | 'mom' | 'child', Person>复制代码
这等价于
interface Family {
parent: Person,
mom: Person,
child: Person
}复制代码
/**
* Exclude null and undefined from T
*/
type NonNullable<T> = T extends null | undefined ? never : T;复制代码
至此,
但到目前为止, 咱们处理的场景都限与非函数的 interface(class)/type, 对于函数的 interface, 咱们可否进行一些特殊处理, 好比, 对一个已有的函数定义, 提取其第 2 个参数的参数类型? 对于下面这个 func, 咱们可否提取出 arg2 的类型?
interface func () {
(arg1: string, arg2: {
bar: string
}): void
}复制代码
下一篇咱们会讨论, 如何使用 infer 关键字达成这一目标.
基于泛型的类型推导, 理论上从 typescript 2.8 开始(实际上更早, 但 typescript 2.8/3.5 是具备里程碑意义的版本, 故以此划分)就能够实现了, 从 typescript 3.5, 官方内置了一些用于推导的的类型(#type)和接口(#interface), 这些是咱们用于学习类型推导的良好案例. 本文用到的全部例子, 都来自于 typescript 内置的 lib.es5.d.ts
.
注意 对于泛型, T
每每是用做泛型标记的第一个选择, T
之于泛型, 比如 foo
之于样例代码