因为flow的类型本身是固定的,如果想要对类型进行转换和操作,就得借助于工具类型(Utility Types),有了它们,我们可以更加灵活地使用flow。
类型
$Keys
$Keys顾名思义,是用于获取一个类型的键值,类似与Object.keys。注意$Keys<T>返回的是一个联合类型(Union Type),而不是数组。
1 | // @flow |
上面创建一个gender的对象(类似于枚举),如果有个方法需要加入性别文字的类型声明,就需要添加枚举对象中的每一个键值,这个显然是很笨拙的方式。这时$Keys就派上用场了。
1 | // @flow |
$Values
了解了$Keys,$Values也变得简单了,接着上面的示例,如果我们的函数接受的是枚举值,而不是键值文本时。
1 | // @flow |
等等,细心一点就会发现,doGender(2)怎么也没有抛错呢!
让我们回忆一下上文中关于typeof类型的一个示例。
值得注意的是,如果对文字没有显式声明类型,flow会将其推导成基本类型,所以
typeof得出的也是基本类型。但是如果对文字显式声明了文字类型,typeof就会得到显式声明的类型。
由于没有对gender显式声明类型,内部的字段都被推导成基本类型了,所以我们要加以改造。
1 | // @flow |
显式的声明gender的类型之后,doGener如预期的抛出了错误。
$ReadOnly
$ReadOnly正如其名是将一个对象类型的字段变成只读。
1 | // @flow |
等价于
1 | // @flow |
$Exact
$Exact<{name: string}> 等价于 {| name: string |},用于严格限制对象属性。
$Diff<A, B>
$Diff<A, B>返回一个A中存在但B中不存在的属性,相当于A / B。注意如果B中包含A中没有的属性,那么$Diff会抛出错误。
1 | // @flow |
现在我们看一个正确的示例
1 | // @flow |
看起来是不是有点眼熟,没错,这就是React的定义文件处理props的方式。
如果B中某个属性在A中不存在,但这个属性是可选参数,那么也是可以的。
1 | type A = $Diff<{}, {nope: number}>; // Error |
$Rest<A, B>
在es6中新增了Object Spread语法,如
1 | const obj = { |
$Rest<A, B>与其类似,A和B都是Object类型
1 | // @flow |
表面上看$Rest<A, B>和$Diff<A, B>功能是差不多的,实际上它们的区别在于对对象自身的属性的处理。
1 | // @flow |
先看看这个示例,对他们之间的区别应该会有个大概的理解。
1 | function Parent(){ |
可以看到Object Spread并没有处理原型上的数据,仅是对字面量结构进行了展开,也就是说只会处理对象自身的属性。
flow会将声明成严格匹配对象类型(Exact Object Type)的属性当做是
ownProps(对象自身的属性)。
所以flow的为了描述真实的Object Spread操作,需要明确的限定这个对象类型的边界,否则flow会认为这个对象可能存在其他属性。举个示例,由于{}中仍然可能有n属性,所以$Rest<{|n: number|}, {}>会返回{|n?: number|},而$Diff<{|n: number|}, {}>会返回{|n: number|}。
同时我们还必须指明被展开对象类型是严格匹配对象类型,否则会造成展开后对象上的属性丢失。例如 $Rest<{n: number}, {||}>会返回{|n?: number|},因为n属性可能存在于对象的原型上,展开后在剩余属性上就并不存在。
$PropertyType<T, k>
此工具类型用于获取对象类型的一个属性的类型。k必须是一个文本字符串。
1 | // @flow |
在react中获取props:
1 | // @flow |
它还支持嵌套操作:
1 | //... |
如果想要获取类的静态属性,可以使用Class<T>
1 | // @flow |
$ElementType<T, K>
这个工具也是用来获取T的属性的类型,和$PropertyType不同点在于参数K可以为声明类型,而不只是字符串文本,并且它可以作用于array,tuple,object类型。
1 | // @flow |
$ElementType更强大的地方在于可以K可以传入泛型(generics):
1 | // @flow |
$NonMaybeType
顾名思义,此工具用于将一个Maybe Type转换成非Maybe Type。
1 | // @flow |
$ObjMap<T, F>
$ObjMap<T, F>传入一个对象类型T和一个泛型函数类型F,返回一个新的对象类型,新对象类型的键和T一样,而键值是F的返回值。F会传入了键值的类型V作为泛型参数。
示例:
1 | //@flow |
$TupleMap<T, F>
上面是对象,而这个是用来操作可迭代类型Tuple和Array,功能类型与javascript中数组的map方法。
1 | // @flow |
$Call
$Call<F>用于调用一个函数类型,返回函数类型的返回类型。通过它我们可以在运行时创建多种不同的类型,而不需要一个个写静态类型。
1 | // @flow |
#Class
上面已经见过了,用于获取一个Class类型的类型。如果一个变量是类本身,就需要使用Class<T>来声明,因为直接用类来声明的是类的实例。
1 | // @flow |
$Shape
复制对象类型对象的属性,并将其设置成可选类型(注意不是Maybe Type)。
1 | // @flow |
Existential Type (*)
*和any不同,*会告诉flow去推导这个类型,所以在一定程度上可以避免丢失类型安全。
1 | // @flow |
记住*并不是总能推导出类型,如果上下文无法推断,那么*就和any无异。
$Subtype 和 $Supertype
虽然在官方文档中显示为Work in Progress,其实这两个工具在很多模块已经在使用了。
在弄清Subtype和Supertype的概念之前,先把它俩搁置一下吧。
结语
参考文档: