有条件类型
TypeScript 2.8引入了_有条件类型_,它能够表示非统一的类型。 有条件的类型会以一个条件表达式进行类型关系检测,从而在两种类型中选择其一:
ts
上面的类型意思是,若T能够赋值给U,那么类型是X,否则为Y。
有条件的类型T extends U ? X : Y或者_解析_为X,或者_解析_为Y,再或者_延迟_解析,因为它可能依赖一个或多个类型变量。 是否直接解析或推迟取决于:
- 首先,令T'和U'分别为T和U的实例,并将所有类型参数替换为any,如果T'不能赋值给U',则将有条件的类型解析成Y。直观上讲,如果最宽泛的T的实例不能赋值给最宽泛的U的实例,那么我们就可以断定不存在可以赋值的实例,因此可以解析为Y。
- 其次,针对每个在U内由推断声明引入的类型变量,依据从T推断到U来收集一组候选类型(使用与泛型函数类型推断相同的推断算法)。对于给定的推断类型变量V,如果有候选类型是从协变的位置上推断出来的,那么V的类型是那些候选类型的联合。反之,如果有候选类型是从逆变的位置上推断出来的,那么V的类型是那些候选类型的交叉类型。否则V的类型是never。
- 然后,令T''为T的一个实例,所有推断的类型变量用上一步的推断结果替换,如果T''_明显可赋值_给U,那么将有条件的类型解析为X。除去不考虑类型变量的限制之外,_明显可赋值_的关系与正常的赋值关系一致。直观上,当一个类型明显可赋值给另一个类型,我们就能够知道它可以赋值给那些类型的_所有_实例。
- 否则,这个条件依赖于一个或多个类型变量,有条件的类型解析被推迟进行。
例子
ts
分布式有条件类型
如果有条件类型里待检查的类型是naked type parameter,那么它也被称为“分布式有条件类型”。 分布式有条件类型在实例化时会自动分发成联合类型。 例如,实例化T extends U ? X : Y,T的类型为A | B | C,会被解析为(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)。
例子
ts
在T extends U ? X : Y的实例化里,对T的引用被解析为联合类型的一部分(比如,T指向某一单个部分,在有条件类型分布到联合类型之后)。 此外,在X内对T的引用有一个附加的类型参数约束U(例如,T被当成在X内可赋值给U)。
例子
ts
注意在Boxed<T>的true分支里,T有个额外的约束any[],因此它适用于T[number]数组元素类型。同时也注意一下有条件类型是如何分布成联合类型的。
有条件类型的分布式的属性可以方便地用来_过滤_联合类型:
ts
有条件类型与映射类型结合时特别有用:
ts
与联合类型和交叉类型相似,有条件类型不允许递归地引用自己。比如下面的错误。
例子
ts
有条件类型中的类型推断
现在在有条件类型的extends子语句中,允许出现infer声明,它会引入一个待推断的类型变量。 这个推断的类型变量可以在有条件类型的true分支中被引用。 允许出现多个同类型变量的infer。
例如,下面代码会提取函数类型的返回值类型:
ts
有条件类型可以嵌套来构成一系列的匹配模式,按顺序进行求值:
ts
下面的例子解释了在协变位置上,同一个类型变量的多个候选类型会被推断为联合类型:
ts
相似地,在抗变位置上,同一个类型变量的多个候选类型会被推断为交叉类型:
ts
当推断具有多个调用签名(例如函数重载类型)的类型时,用_最后_的签名(大概是最自由的包含所有情况的签名)进行推断。 无法根据参数类型列表来解析重载。
ts
无法在正常类型参数的约束子语句中使用infer声明:
ts
但是,可以这样达到同样的效果,在约束里删掉类型变量,用有条件类型替换:
ts
预定义的有条件类型
TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:
- Exclude<T, U>— 从- T中剔除可以赋值给- U的类型。
- Extract<T, U>— 提取- T中可以赋值给- U的类型。
- NonNullable<T>— 从- T中剔除- null和- undefined。
- ReturnType<T>— 获取函数返回值类型。
- InstanceType<T>— 获取构造函数类型的实例类型。
Example
ts
注意:
Exclude类型是建议的Diff类型的一种实现。我们使用Exclude这个名字是为了避免破坏已经定义了Diff的代码,并且我们感觉这个名字能更好地表达类型的语义。我们没有增加Omit<T, K>类型,因为它可以很容易的用Pick<T, Exclude<keyof T, K>>来表示。
改进对映射类型修饰符的控制
映射类型支持在属性上添加readonly或?修饰符,但是它们不支持_移除_修饰符。 这对于同态映射类型有些影响,因为同态映射类型默认保留底层类型的修饰符。
TypeScript 2.8为映射类型增加了增加或移除特定修饰符的能力。 特别地,映射类型里的readonly或?属性修饰符现在可以使用+或-前缀,来表示修饰符是添加还是移除。
例子
ts
不带+或-前缀的修饰符与带+前缀的修饰符具有相同的作用。因此上面的ReadonlyPartial<T>类型与下面的一致
ts
利用这个特性,lib.d.ts现在有了一个新的Required<T>类型。 它移除了T的所有属性的?修饰符,因此所有属性都是必需的。
例子
ts
注意在--strictNullChecks模式下,当同态映射类型移除了属性底层类型的?修饰符,它同时也移除了那个属性上的undefined类型:
例子
ts
改进交叉类型上的keyof
TypeScript 2.8作用于交叉类型的keyof被转换成作用于交叉成员的keyof的联合。 换句话说,keyof (A & B)会被转换成keyof A | keyof B。 这个改动应该能够解决keyof表达式推断不一致的问题。
例子
ts
更好的处理.js文件中的命名空间模式
TypeScript 2.8加强了识别.js文件里的命名空间模式。 JavaScript顶层的空对象字面量声明,就像函数和类,会被识别成命名空间声明。
js
顶层的赋值应该有一致的行为;也就是说,var或const声明不是必需的。
js
立即执行的函数表达式做为命名空间
立即执行的函数表达式返回一个函数,类或空的对象字面量,也会被识别为命名空间:
js
默认声明
“默认声明”允许引用了声明的名称的初始化器出现在逻辑或的左边:
js
原型赋值
你可以把一个对象字面量直接赋值给原型属性。独立的原型赋值也可以:
ts
嵌套与合并声明
现在嵌套的层次不受限制,并且多文件之间的声明合并也没有问题。以前不是这样的。
js
各文件的JSX工厂
TypeScript 2.8增加了使用@jsx dom指令为每个文件设置JSX工厂名。 JSX工厂也可以使用--jsxFactory编译参数设置(默认值为React.createElement)。TypeScript 2.8你可以基于文件进行覆写。
例子
ts
生成:
js
本地范围的JSX命名空间
JSX类型检查基于JSX命名空间里的定义,比如JSX.Element用于JSX元素的类型,JSX.IntrinsicElements用于内置的元素。 在TypeScript 2.8之前JSX命名空间被视为全局命名空间,并且一个工程只允许存在一个。 TypeScript 2.8开始,JSX命名空间将在jsxNamespace下面查找(比如React),允许在一次编译中存在多个jsx工厂。 为了向后兼容,全局的JSX命名空间被当做回退选项。 使用独立的@jsx指令,每个文件可以有自己的JSX工厂。
新的--emitDeclarationsOnly
--emitDeclarationsOnly允许_仅_生成声明文件;使用这个标记.js/.jsx输出会被跳过。当使用其它的转换工具如Babel处理.js输出的时候,可以使用这个标记。