内置的类型
Flow 包含很多内置类型来描述 JavasScript 中的值
有些是给原生类型用的,像 number
和 string
。
any
和 mixed
比较宽松,没有把值的类型限定死,而其他字面类型则描述某一种类型。
Flow 支持标准 js,浏览器 api 和 Node.js 标准库,所以开箱即用
注意使用类型转换语法#
为了方便讲解,下面例子大量使用类型转换:
Flow 的类型转换目测就那么回事:
表达式 e
和类型 T
组成 (e:T)
,如果 e 不属于 T
类型(子集)则会报类型错误
换个说法,要检测一个强制转换的用法是否正确,只需要把它当做变量或者参数的类型注解来检测就行了。
举个例子,如果 var x:T = e
合法,意味着 (e:T)
同样合法
1 2 3 4 5 6 7 8 9 |
(1 + 1: string); // Error: Numbers are not strings ("Hello, World": string); // OK: Strings are strings class A {} class B extends A {} let a = new A(), b = new B(); (b: A); // OK: B is a subclass of A (a: B); // Error: A is not a subclass of B |
$> flow
1: (1 + 1: string); // Error: Numbers are not strings
^^^^^ number. This type is incompatible with
1: (1 + 1: string); // Error: Numbers are not strings
^^^^^^ string
9: (a: B); // Error: A is not a subclass of B
^ A. This type is incompatible with
9: (a: B); // Error: A is not a subclass of B
^ B
boolean#
这个类型用来描述 JavaScript 的布尔值,这个类型的值就只有 true
和 false
1 2 3 |
(true: boolean); (false: boolean); ("foo": boolean); // strings are not booleans |
$> flow
3: ("foo": boolean); // strings are not booleans
^^^^^ string. This type is incompatible with
3: ("foo": boolean); // strings are not booleans
^^^^^^^ boolean
Javascript 允许把某些非布尔值当做布尔值来用。Flow 理解也允许用在 if
作为条件,
或者是 &&
之类的逻辑运算。如果你需要强制把对象转化为 boolean
,
可以用 js 自带的 Boolean
方法
1 2 3 |
function takes_boolean(x: boolean): void {} takes_boolean(0); // Implicit casting is an error. takes_boolean(Boolean(0)); // Adding an explicit cast type checks. |
$> flow
2: takes_boolean(0); // Implicit casting is an error.
^^^^^^^^^^^^^^^^ function call
2: takes_boolean(0); // Implicit casting is an error.
^ number. This type is incompatible with
1: function takes_boolean(x: boolean): void {}
^^^^^^^ boolean
注意 boolean
和 Boolean
是不同的,前者是原生类型的布尔,就是程序中的字面量
true
和 false
,或者是类似 a === b
这类表达式的值。而后者是一个包装对象,
很少用到。
1 2 |
(true: Boolean); (new Boolean(false): Boolean); |
$> flow
1: (true: Boolean);
^^^^ boolean. This type is incompatible with
1: (true: Boolean);
^^^^^^^ Boolean
number#
JavaScript 只有一种数字类型,就是 IEEE 754 浮点数。
number
类型就是描述它的,包括 Infinity
和 NaN
(3.14: number);
(42: number);
(NaN: number);
(parseFloat("not a number"): number); // hint: NaN
注意:number
和 Number
是不同的。前者是原生数字类型,像代码里的 3.14
和 42
,
或者是 parseFlow(input.value)
这类表达式的值。而后者是 Number 包装对象,很少用到。
1 2 |
(0: Number); (new Number(0): Number); |
$> flow
1: (0: Number);
^ number. This type is incompatible with
1: (0: Number);
^^^^^^ Number
string#
("foo": string);
("bar": string);
一般来说,类型静默转换在 Flow 中是会报错的。
不过,将 字符串
和 数字
用 +
号连接生成字符串, 在 JS 中很常见。
所以 Flow 也接受这种方式。
((100 + "%") : string);
注意:string
和 String
是不同类型。前者是原生类型字符串,像程序中的 "foo"
和
"bar"
,或者是 ""+42
这类表达式的返回值,而后者就是一个字符串包装类,很少用到。
1 2 |
("foo": String); (new String("foo"): String); |
$> flow
1: ("foo": String);
^^^^^ string. This type is incompatible with
1: ("foo": String);
^^^^^^ String
null and void#
JavaScript 有 null
和 undefined
,Flow 中, null
(值) 有 null
类型,
undefined
有 void
类型
1 2 3 4 5 |
(null: null); // yup (null: void); // nope (undefined: void); // yup (undefined: null); // nope |
$> flow
2: (null: void); // nope
^^^^ null. This type is incompatible with
2: (null: void); // nope
^^^^ undefined
5: (undefined: null); // nope
^^^^^^^^^ undefined. This type is incompatible with
5: (undefined: null); // nope
^^^^ null
对于可选的对象属性和可选的方法参数用 T|void
,有些用 T
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function optional_fun(foo?: string) { (foo: string|void); } optional_fun("foo"); optional_fun(undefined); optional_fun(); optional_fun(null); // null is not a string, nor void type optional_obj = { foo?: string } ({foo: "foo"}: optional_obj); ({foo: undefined}: optional_obj); ({}: optional_obj); ({foo: null}: optional_obj); // null is not a string, nor void |
$> flow
7: optional_fun(null); // null is not a string, nor void
^^^^^^^^^^^^^^^^^^ function call
7: optional_fun(null); // null is not a string, nor void
^^^^ null. This type is incompatible with
1: function optional_fun(foo?: string) {
^^^^^^ string
13: ({foo: null}: optional_obj); // null is not a string, nor void
^^^^ null. This type is incompatible with
9: type optional_obj = { foo?: string }
^^^^^^ string
方法的参数如果有默认值就意味着是可选的,不过只针对调用的时候是可选类型,一旦进入
方法体,这个参数值就是 非-void
类型
function default_fun(foo: string = "default foo") {
(foo: string);
}
default_fun("foo");
default_fun(undefined);
default_fun();
对于可能是某个类型 T
的,还可以这样 T|void|null
表示可选
function maybe_fun(foo: ?string) {
(foo: string|void|null);
}
maybe_fun("foo");
maybe_fun(undefined);
maybe_fun();
maybe_fun(null);
any#
any
是所有类型的超集,同时也是所有类型的子集。
如果有些地方目测就一定是某种类型,那么 Flow 肯定也能看得出来,给你提醒。
function takes_any(x: any): void {}
takes_any(0);
takes_any("");
takes_any({ foo: "bar" });
declare var unsafe: any;
(unsafe: number);
(unsafe: string);
(unsafe: { foo: string });
除了关注类型的匹配外,对 any
类型值的操作的相关的概念也是值得思考的。
对于 any
类型的值,通过任何属性访问,返回值依然是 any
类型。
你甚至可以把 any
类型的值当做方法来用,传入任意多个任意类型的参数。
unsafe.foo.bar.baz;
(unsafe("foo"): string);
(unsafe("bar"): number);
你可以认为 any
类型就是某种类型系统的后门。
使用 any
是有潜在危险的,尽可能避免。
不过,有时候用它也是很靠谱的,例如,当你对现有代码添加类型,
使用 any
可以帮助你平缓过渡到类型检测。
同样,对第三方模块声明 any
也方便整合。
最后,由于 JS 极其动态的特性,有些情况 Flow 是无法解析的,
而 any
的原则就是把不确定类型的值变成有类型
mixed#
mixed
跟 any
一样,是所有类型的超集,但不是所有类型子集。
这意味着 mixed
稍微安全版的 any
,尽量能用 mixed
就不用 any
。
1 2 3 4 5 6 7 8 9 |
function takes_mixed(x: mixed): void {} takes_mixed(0); takes_mixed(""); takes_mixed({ foo: "bar" }); function returns_mixed(): mixed {} (returns_mixed(): number); (returns_mixed(): string); (returns_mixed(): { foo: string }); |
$> flow
7: (returns_mixed(): number);
^^^^^^^^^^^^^^^ mixed. This type is incompatible with
7: (returns_mixed(): number);
^^^^^^ number
8: (returns_mixed(): string);
^^^^^^^^^^^^^^^ mixed. This type is incompatible with
8: (returns_mixed(): string);
^^^^^^ string
9: (returns_mixed(): { foo: string });
^^^^^^^^^^^^^^^ mixed. This type is incompatible with
9: (returns_mixed(): { foo: string });
^^^^^^^^^^^^^^^ object type
不过还是有可能用 mixed
表示一个值,不过你得
refine 这个值
例如,我们构造某种值的类型,能直接用 JSON 表示。 自然会这样表达这种类型:
type JSON = | string | number | boolean | null | JSONObject | JSONArray;
type JSONObject = { [key:string]: JSON };
type JSONArray = Array<JSON>;
给定一个方法,验证它的参数是不是 JSON 格式。
如果我们把参数类型声明为 any
,那么我们可以直接返回参数,因为 any
是任意类型的
子集,所以 Flow 不会报错。
如果我们用的是 mixed
,虽然我们可以合法的传入参数,毕竟 mixed
是所有类型的超集,
但是为了要满足返回值为 JSON
类型,Flow 就要求我们实现必要的条件判断,因为 mixed
不是所有类型的子集
function typedJSON(x: mixed): JSON {
if (typeof x === "object" && x !== null) {
let o: JSONObject = {};
for (let k of Object.keys(x)) {
o[k] = typedJSON(x[k]);
}
return o;
}
if (Array.isArray(x)) {
return x.map(typedJSON);
}
if (x === null ||
typeof x === "string" ||
typeof x === "number" ||
typeof x === "boolean") {
return x;
}
throw new Error("Invalid JSON");
}
字面量类型#
boolean
, number
, 和 string
类型分别表示 true
和 false
,
任意数字,任意字符串。
与此同时,定义一个具体值作为类型也是很有用的。
这个功能表现出惊人的灵活多样: 字面量类型可以用来表示枚举类型或其他不相交的并集,
还有一些公用的类型,用来补充表示 boolean
, number
, 和 string
外的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
("foo": "foo"); ("bar": "foo"); // `"bar"` is not exactly `"foo"` ("fo"+"o": "foo"); // even simple expressions lose literal information (1: 1); (2: 1); // `2` is not exactly `1` (1+1: 2); // even simple expressions lose literal information (true: true); (true: false); // `true` is not exactly `false` // boolean expressions *do* preserve literal information (!true: false); (true && false: false); (true || false: true); |
$> flow
2: ("bar": "foo"); // `"bar"` is not exactly `"foo"`
^^^^^ string. Expected string literal `foo`, got `bar` instead
2: ("bar": "foo"); // `"bar"` is not exactly `"foo"`
^^^^^ string literal `foo`
3: ("fo"+"o": "foo"); // even simple expressions lose literal information
^^^^^^^^ string. Expected string literal `foo`
3: ("fo"+"o": "foo"); // even simple expressions lose literal information
^^^^^ string literal `foo`
6: (2: 1); // `2` is not exactly `1`
^ number. Expected number literal `1`, got `2` instead
6: (2: 1); // `2` is not exactly `1`
^ number literal `1`
7: (1+1: 2); // even simple expressions lose literal information
^^^ number. Expected number literal `2`
7: (1+1: 2); // even simple expressions lose literal information
^ number literal `2`
10: (true: false); // `true` is not exactly `false`
^^^^ boolean. Expected boolean literal `false`, got `true` instead
10: (true: false); // `true` is not exactly `false`
^^^^^ boolean literal `false`
我们用字面类型弄点有意思的,下面的程序展示了字面量类型可以定义对象的属性类型。 同事也展示了元组类型怎么作枚举类型用。
type Suit =
| "Diamonds"
| "Clubs"
| "Hearts"
| "Spades";
type Rank =
| 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
| "Jack"
| "Queen"
| "King"
| "Ace";
type Card = {
suit: Suit,
rank: Rank,
}
declare var cards: Card[];
cards.sort((a, b) => cardComparator(a, b));
cards.sort((a, b) => cardComparator(a, b, true)); // Aces high
function suitOrder(suit) {
return {
Diamonds: 0,
Clubs: 1,
Hearts: 3,
Spades: 4,
}[suit];
}
function rankOrder(rank, aceHigh = false) {
if (typeof rank === "string") {
return {
Jack: 11,
Queen: 12,
King: 13,
Ace: aceHigh ? 14 : 1,
}[rank];
} else {
return rank;
}
}
function cardComparator(a, b, aceHigh?) {
return (rankOrder(a.rank, aceHigh) - rankOrder(b.rank, aceHigh))
|| (suitOrder(a.suit) - suitOrder(b.suit));
}
注意,Flow 能够探测出 suitOrder
, rankOrder
, 和 cardComparator
的参数以及返回类型。
可以试试把这些代码拷贝到编辑器重现错误,看看 Flow 能捕获到什么。
JavaScript 标准库#
Flow 支持 JavaSript标准 类型
Flow 广泛支持标准库,包括遍历、迭代器和生成器。
浏览器接口#
Flow 支持 文档对象模型 (DOM), 浏览器对象模型 (BOM), 以及 样式对象模型 (CSSOM) 的类型
Node.js#
Flow 支持 Node.js 的类型
You can edit this page on GitHub and send us a pull request!