内置的类型

Flow 包含很多内置类型来描述 JavasScript 中的值

有些是给原生类型用的,像 numberstringanymixed 比较宽松,没有把值的类型限定死,而其他字面类型则描述某一种类型。

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
show Flow output hide Flow output
$> 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 的布尔值,这个类型的值就只有 truefalse

1
2
3
(true: boolean);
(false: boolean);
("foo": boolean); // strings are not booleans
show Flow output hide Flow output
$> 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.
show Flow output hide Flow output
$> 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

注意 booleanBoolean 是不同的,前者是原生类型的布尔,就是程序中的字面量 truefalse,或者是类似 a === b 这类表达式的值。而后者是一个包装对象, 很少用到。

1
2
(true: Boolean);
(new Boolean(false): Boolean);
show Flow output hide Flow output
$> flow
1: (true: Boolean);
    ^^^^ boolean. This type is incompatible with
1: (true: Boolean);
          ^^^^^^^ Boolean

number#

JavaScript 只有一种数字类型,就是 IEEE 754 浮点数。 number 类型就是描述它的,包括 InfinityNaN

(3.14: number);
(42: number);
(NaN: number);

(parseFloat("not a number"): number); // hint: NaN

注意:numberNumber 是不同的。前者是原生数字类型,像代码里的 3.1442, 或者是 parseFlow(input.value) 这类表达式的值。而后者是 Number 包装对象,很少用到。

1
2
(0: Number);
(new Number(0): Number);
show Flow output hide Flow output
$> flow
1: (0: Number);
    ^ number. This type is incompatible with
1: (0: Number);
       ^^^^^^ Number

string#

("foo": string);
("bar": string);

一般来说,类型静默转换在 Flow 中是会报错的。 不过,将 字符串数字+ 号连接生成字符串, 在 JS 中很常见。 所以 Flow 也接受这种方式。

((100 + "%") : string);

注意:stringString 是不同类型。前者是原生类型字符串,像程序中的 "foo""bar",或者是 ""+42 这类表达式的返回值,而后者就是一个字符串包装类,很少用到。

1
2
("foo": String);
(new String("foo"): String);
show Flow output hide Flow output
$> flow
1: ("foo": String);
    ^^^^^ string. This type is incompatible with
1: ("foo": String);
           ^^^^^^ String

null and void#

JavaScript 有 nullundefined,Flow 中, null(值) 有 null 类型, undefinedvoid 类型

1
2
3
4
5
(null: null); // yup
(null: void); // nope

(undefined: void); // yup
(undefined: null); // nope
show Flow output hide Flow output
$> 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
show Flow output hide Flow output
$> 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#

mixedany 一样,是所有类型的超集,但不是所有类型子集。 这意味着 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 });
show Flow output hide Flow output
$> 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 类型分别表示 truefalse, 任意数字,任意字符串。 与此同时,定义一个具体值作为类型也是很有用的。

这个功能表现出惊人的灵活多样: 字面量类型可以用来表示枚举类型或其他不相交的并集,

还有一些公用的类型,用来补充表示 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);
show Flow output hide Flow output
$> 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 的类型

← Prev Next →

You can edit this page on GitHub and send us a pull request!