因为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
的概念之前,先把它俩搁置一下吧。
结语
参考文档: