js静态类型解析flow用法

原由

遍寻百度,google,没发现flow的中文文档,这对国内显然是不友好的,虽然说flow 平时用不着, 可是通常框架都会用一下,以便用户能够准确的使用框架,能够避免不少谜同样的BUG,既然没有,那我就来翻译一下咯.计划先翻译类型注释(types annotations)部分,安装的一搜一大把.javascript

flow 类型注释

当你的类型不注释的时候, flow 就不起做用了,so 来看看 flow 类型 能够如何注释. 可不是 // 这个注释java

原始类型

javascript 一共有6钟原始数据类型.git

  • Booleans
  • Strings
  • Numbers
  • null
  • undefined (void in Flow types)
  • Symbols (new in ECMAScript 2015, not yet supported in Flow) flow 不支持symbols

原始类型分两种,一种是字面量的, 一种是包装过的 好比 3 跟 Number(3);
好比下面这样,你只能传 字面量.booleans 除外es6

// @flow
function method(x: number, y: string, z: boolean) {
  // ...
}

method(3.14, "hello", true);

booleans

flow 能够识别 !!x 和 Boolean(0) 的明确类型转换的booleanexpress

// @flow
function acceptsBoolean(value: boolean) {
  // ...
}

acceptsBoolean(0);          // Error! 错误
acceptsBoolean(Boolean(0)); // Works! OK
acceptsBoolean(!!0);        // Works! OK

number

数字就很明确了数组

// @flow
function acceptsNumber(value: number) {
  // ...
}

acceptsNumber(42);       // Works!
acceptsNumber(3.14);     // Works!
acceptsNumber(NaN);      // Works!
acceptsNumber(Infinity); // Works!
acceptsNumber("foo");    // Error!

string

// @flow
function acceptsString(value: string) {
  // ...
}

acceptsString("foo"); // Works!
acceptsString(false); // Error!

js中 字符串会有隐藏的转换安全

"foo" + 42; // "foo42"
    "foo" + {}; // "foo[object Object]"

flow 只支持数字和字符串的隐藏转换数据结构

// @flow
    "foo" + "foo"; // Works!
    "foo" + 42;    // Works!
    "foo" + {};    // Error!
    "foo" + [];    // Error!

若是你要使用, 必需要明确转换框架

// @flow
    "foo" + String({});     // Works!
    "foo" + [].toString();  // Works!
    "" + JSON.stringify({}) // Works!

null 和 undefined

在flow中 undefined 是 voiddom

// @flow
    function acceptsNull(value: null) {
      /* ... */
    }

    function acceptsUndefined(value: void) {
      /* ... */
    }
    acceptsNull(null);      // Works!
    acceptsNull(undefined); // Error!
    acceptsUndefined(null);      // Error!
    acceptsUndefined(undefined); // Works!

可能的类型

可能的类型, 是要用再 那些可选的值, 你可使用一个问号来标记他, 证实这个值是可选的,并非必须的

// @flow
    function acceptsMaybeString(value: ?string) {
      // ...
    }

    acceptsMaybeString("bar");     // Works!
    acceptsMaybeString(undefined); // Works!
    acceptsMaybeString(null);      // Works!
    acceptsMaybeString();          // Works!

对象属性选项

你能够用一个问号来表示该对象的某个属性是无关紧要的

// @flow
    function acceptsObject(value: { foo?: string }) {
      // ...
    }

    acceptsObject({ foo: "bar" });     // Works!
    acceptsObject({ foo: undefined }); // Works!
    acceptsObject({ foo: null });      // Error!
    acceptsObject({});                 // Works!

这个值能够是undefined 可是 他不能是null

函数参数的选项

加个问号标明,这个函数的参数可可选的

// @flow
    function acceptsOptionalString(value?: string) {
      // ...
    }

    acceptsOptionalString("bar");     // Works!
    acceptsOptionalString(undefined); // Works!
    acceptsOptionalString(null);      // Error!
    acceptsOptionalString();          // Works!

函数的默认参数

es5 的新特性

// @flow
    function acceptsOptionalString(value: string = "foo") {
      // ...
    }

    acceptsOptionalString("bar");     // Works!
    acceptsOptionalString(undefined); // Works!
    acceptsOptionalString(null);      // Error!
    acceptsOptionalString();          // Works!

symbol

flow未支持

字面类型

flow 不止能够指定类型, 他还能够指定某个特定的值. 很是牛掰

如:

// @flow
    function acceptsTwo(value: 2) {
      // ...
    }

    acceptsTwo(2);   // Works!
    // $ExpectError
    acceptsTwo(3);   // Error!
    // $ExpectError
    acceptsTwo("2"); // Error!

如:

// @flow
function getColor(name: "success" | "warning" | "danger") {
  switch (name) {
    case "success" : return "green";
    case "warning" : return "yellow";
    case "danger"  : return "red";
  }
}
getColor("success"); // Works!
getColor("danger");  // Works!
// $ExpectError
getColor("error");   // Error!

杂交类型 (mixed types)

你能够匹配多个类型

function stringifyBasicValue(value: string | number) {
      return '' + value;
    }

你能够像java 的泛型同样(有区别)去标明一个类型,下面的例子 标明,该函数返回的类型跟传进函数的类型相同.

function identity<T>(value: T): T {
  return value;
}

你能够这样来 标记 一个函数能够接受任何类型的参数

function getTypeOf(value: mixed): string {
      return typeof value;
    }

当你使用 mixed 时候, 虽然你能够传进任何类型, 可是你返回的时候 必需要明确他是什么类型, 否则就报错

// @flow
    function stringify(value: mixed) {
      // $ExpectError
      return "" + value; // Error!
    }

    stringify("foo");

任何类型(any type)

不要搞混any 和mixed, 若是你想跳过类型检查,那你就用 any 吧

// @flow
    function add(one: any, two: any): number {
      return one + two;
    }

    add(1, 2);     // Works.
    add("1", "2"); // Works.
    add({}, []);   // Works.

只要两种状况,可使用 any

  1. 旧代码 新增flow 类型检查,而且 用其余类型的会引发大量错误
  2. 当你明确的知道你的代码不能经过类型检查的时候,

避免泄漏any

当你声明了 传进的参数的any的时候,那么你返回的参数也都是any , 避免这种状况, 须要切断 它

// @flow
    function fn(obj: any) /* (:number) */ {
      let foo: number = obj.foo; // 这句才是重点, 切断 any
      let bar /* (:number) */ = foo * 2;
      return bar;
    }

    let bar /* (:number) */ = fn({ foo: 2 });
    let baz /* (:string) */ = "baz:" + bar;

可能类型 (maybe type)

就是上面提到的 能够用 ? 问号标记他是可选的类型

变量类型 (variable type)

  • var - 声明一个变量,选择性赋值
  • let - 声明一个块级变量,选择性辅助
  • const - 声明一个块级变量,并赋值,且不能再次赋值

在flow 分为两组, 一组是 let 和 var 能够再次赋值, 另外一组是const 不能再次赋值

const

const 能够注入你赋值的类型, 或者你本身手动的指定类型

// @flow
    const foo /* : number */ = 1;
    const bar: number = 2;

let 和 var

跟上面同样, 这两个也能够自动的注入类型

可是 若你自动注入的类型, 你从新赋值修改的类型的时候并不会获得报错

若是语句,函数,和其余的条件代码,能够精确的指出他是什么类型,那么就能够避免flow 的检查 否则就报错

// @flow
        let foo = 42;

        function mutate() {
          foo = true;
          foo = "hello";
        }

        mutate();

        // $ExpectError
        let isString: string = foo; // Error!

尽可能避免上面的用法(我的推荐)

函数类型(function type)

函数只有两种用法, 要么参数, 要么返回值

// @flow
    function concat(a: string, b: string): string {
      return a + b;
    }

    concat("foo", "bar"); // Works!
    // $ExpectError
    concat(true, false);  // Error!

声明函数

同上

箭头函数

(str: string, bool?: boolean, ...nums: Array<number>) => void

带回调的箭头函数

function method(callback: (error: Error | null, value: string | null) => void) {
      // 上面代表, 这和函数接受的参数 只能是 error null 和 string 而后染回一个 undefined
    }

可选参数

// @flow
    function method(optionalValue?: string) {
      // ...
    }

    method();          // Works.
    method(undefined); // Works.
    method("string");  // Works.
    // $ExpectError
    method(null);      // Error!

剩余参数的用法 (rest parameter)

function method(...args: Array<number>) {
      // ...  使相似java 泛型的 样子 来声明他的类型
    }

函数返回值

function method(): number {
          // 如果标明了返回类型, 那么你的函数必定要有返回值,若是有条件判断语句,那么每一个条件都要有返回值
        }

函数的this

你不用注释this flow 会自动检测上下文来肯定 this 的类型

function method() {
      return this;
    }

    var num: number = method.call(42);
    // $ExpectError
    var str: string = method.call(42);

可是下面这种状况,flow 就会报错

function truthy(a, b): boolean {
      return a && b;
    }

    function concat(a: ?string, b: ?string): string {
      if (truthy(a, b)) {
        // $ExpectError 问题出现再truthy 上 多是 通过了隐式的类型转换
        return a + b;
      }
      return '';
    }

你能够这样来修复上面的问题, 再truthy 上使用 %check

function truthy(a, b): boolean %checks {
      return !!a && !!b;
    }

    function concat(a: ?string, b: ?string): string {
      if (truthy(a, b)) {
        return a + b;
      }
      return '';
    }

若是你想跳过 flow 的 类型检查 , 除了用any 再函数上你还能够用 Function 不过这是不稳定的,你应该避免使用他

function method(func: Function) {
      func(1, 2);     // Works.
      func("1", "2"); // Works.
      func({}, []);   // Works.
    }

    method(function(a: number, b: number) {
      // ...
    });

对象类型

对象类型语法

// @flow
    var obj1: { foo: boolean } = { foo: true };
    var obj2: {
      foo: number,
      bar: boolean,
      baz: string,
    } = {
      foo: 1,
      bar: true,
      baz: 'three',
    };

可选的对象属性

使用了flow, 对象不能访问不存再的属性, 之前是返回undefined 如今访问报错,若是想知道 访问而且赋值 会不会报错,(我建议你本身试一下)

你可使用 ? 号来标明 这个属性 是可选的,能够为undefined

// @flow
    var obj: { foo?: boolean } = {};

    obj.foo = true;    // Works!
    // $ExpectError
    obj.foo = 'hello'; // Error!

标明了类型的属性, 他们能够是undefined(属性赋值为undefined) 或者 空着不写(空对象,), 可是他们不能够是null

密封对象 (seald objects)

密封对象的概念不懂的 能够去了解一下, 就是这个对象不能够修改,可是引用的对象仍是能够修改的; 在flow中 这种对象知道全部你声明的属性的值的类型

// @flow
    var obj = {
      foo: 1,
      bar: true,
      baz: 'three'
    };

    var foo: number  = obj.foo; // Works!
    var bar: boolean = obj.bar; // Works!
    // $ExpectError
    var baz: null    = obj.baz; // Error!
    var bat: string  = obj.bat; // Error!

并且flow 不容许你往这种对象上面添加新的属性, 否则报错

非密封对象属性的从新赋值

注意了,flow 是静态类型检测工具 并非动态的, 因此他不能在运行时判断你的变量是什么类型的, 因此他只能判断你这个对象是不是你赋值过的类型之一.

// @flow
    var obj = {};

    if (Math.random()) obj.prop = true;
    else obj.prop = "hello";

    // $ExpectError
    var val1: boolean = obj.prop; // Error!
    // $ExpectError
    var val2: string  = obj.prop; // Error!
    var val3: boolean | string = obj.prop; // Works!

普通对象的非肯定属性的类型是不安全的

var obj = {};

    obj.foo = 1;
    obj.bar = true;

    var foo: number  = obj.foo; // Works!
    var bar: boolean = obj.bar; // Works!
    var baz: string  = obj.baz; // Works? // 问题在这里 这里的baz 是不存在的属性, 把他定位string 而且给他一个undefined 是可行的, 可是这部安全, 避免使用

额外对象类型 (extra object type)

在一个指望正常对象类型的地方,传一个有着额外属性的对象是安全的

// @flow
    function method(obj: { foo: string }) {
      // ...
    }

    method({
      foo: "test", // Works!
      bar: 42      // Works!
    });

flow 也支持精确的对象类型, 就是对象不能具备额外的属性;

// @flow
var foo: {| foo: string |} = { foo: "Hello", bar: "World!" }; // Error!

若是你想结合精确对象, 须要用到 type 关键字, 我也不知道这个算不算操做符,应该是flow 底层编译支持的, 原声js 并无这个东西

// @flow

    type FooT = {| foo: string |};
    type BarT = {| bar: number |};

    type FooBarFailT = FooT & BarT; // 经过这个& 操做能够把两种状况合并,匹配到 foo 和 bar 同时都有的对象 并且类型必须跟声明的同样
    type FooBarT = {| ...FooT, ...BarT |};

    const fooBarFail: FooBarFailT = { foo: '123', bar: 12 }; // Error!
    const fooBar: FooBarT = { foo: '123', bar: 12 }; // Works!

对象的maps (objects as maps)

虽然有了maps 这个数据结构 可是把对象看成maps 使用 依然很常见

// @flow
    var o: { [string]: number } = {}; // 制定key值的类型, 能够设置这个类型下的任何key值
    o["foo"] = 0;
    o["bar"] = 1;
    var foo: number = o["foo"];

索引也是一个可选的名字

// @flow
    var obj: { [user_id: number]: string } = {};
    obj[1] = "Julia";
    obj[2] = "Camille";
    obj[3] = "Justin";
    obj[4] = "Mark";

索引能够和命名属性混合

// @flow
    var obj: {
      size: number,
      [id: number]: string // 此处混合了 索引和命名
    } = {
      size: 0
    };

    function add(id: number, name: string) {
      obj[id] = name;
      obj.size++;
    }

对象类型(Object Type)

有时候你想创任意的对象, 那么你就能够传一个空对象,或者一个Object 可是后者是不安全的, 建议避免使用

数组类型

let arr: Array<number> = [1, 2, 3];
    let arr1: Array<boolean> = [true, false, true];
    let arr2: Array<string> = ["A", "B", "C"];
    let arr3: Array<mixed> = [1, true, "three"]

简写

let arr: number[] = [0, 1, 2, 3];
    let arr1: ?number[] = null;   // Works!
    let arr2: ?number[] = [1, 2]; // Works!
    let arr3: ?number[] = [null]; // Error!

?number[] === ?Array<number>

数组的访问时不安全的

// @flow
    let array: Array<number> = [0, 1, 2];
    let value: number = array[3]; // Works.// 这里超出了数组的容量

你能够经过下面这样的作法来避免, flow 并未修复这个问题, 因此须要开发者本身注意

let array: Array<number> = [0, 1, 2];
    let value: number | void = array[1];

    if (value !== undefined) {
      // number
    }

$ReadOnlyArray<T>

这个能够标记一个只能读 不能写的数组

// @flow
    const readonlyArray: $ReadOnlyArray\<number> = [1, 2, 3]

可是引用类型仍是能够写的

// @flow
    const readonlyArray: $ReadOnlyArray<{x: number}> = [{x: 1}];
    readonlyArray[0] = {x: 42}; // Error!
    readonlyArray[0].x = 42; // OK

tuple types

这是一种新的类型, 是一种短的列表,可是时又限制的集合,在 javascript 中这个用数组来声明

// 一个类型对应一个 item
    let tuple1: [number] = [1];
    let tuple2: [number, boolean] = [1, true];
    let tuple3: [number, boolean, string] = [1, true, "three"];

能够把取出的值 赋值给具备同样类型的变量, 若是index 超出了索引范围,那么就会返回undefined 在 flow 中也就是 void

// @flow
    let tuple: [number, boolean, string] = [1, true, "three"];

    let num  : number  = tuple[0]; // Works!
    let bool : boolean = tuple[1]; // Works!
    let str  : string  = tuple[2]; // Works!

若是flow 不知道你要访问的时是那么类型, 那么他忽返回全部可能的类型,

// @flow
    let tuple: [number, boolean, string] = [1, true, "three"];

    function getItem(n: number) {
      let val: number | boolean | string = tuple[n];
      // ...
    }

tuple类型的长度必定要严格等于你声明时候的长度

tuple 不能匹配 数组类型, 这也是他们的差异

tuple 只能用 array 的 join() 方法 其余的都不能够用,不然报错

class type

javascript 的class 再flow 能够是值 也能够是类型

class MyClass {
      // ...
    }

    let myInstance: MyClass = new MyClass();

class 里的字段必定要声明类型了才能够用

// @flow
    class MyClass {
      prop: number;// 若是没有这行, 下的赋值会报错,由于prop 没肯定类型
      method() {
        this.prop = 42;
      }
    }

再外部使用的字段,必需要再class 的块里面声明一次

// @flow
    function func_we_use_everywhere (x: number): number {
      return x + 1;
    }
    class MyClass {
      static constant: number; // 内部声明
      static helper: (number) => number;
      method: number => number;
    }
    MyClass.helper = func_we_use_everywhere
    MyClass.constant = 42 // 外部使用
    MyClass.prototype.method = func_we_use_everywhere

声明而且赋值的语法

class MyClass {
      prop: number = 42;
    }

类的泛型

class MyClass<A, B, C> {
      property: A;
      method(val: B): C {
        // ...
      }
    }

若是你要把class做为一个类型,你声明了几个泛型, 你就要传几个参数

// @flow
    class MyClass<A, B, C> {
      constructor(arg1: A, arg2: B, arg3: C) {
        // ...
      }
    }

    var val: MyClass<number, boolean, string> = new MyClass(1, true, 'three');

别名类型(type aliases)

跟上面提到的 type 关键字同样

// @flow
    type MyObject = {
      foo: number,
      bar: boolean,
      baz: string,
    };

这个是类型别名 能够在不一样的地方复用

// @flow
    type MyObject = {
      // ...
    };

    var val: MyObject = { /* ... */ };
    function method(val: MyObject) { /* ... */ }
    class Foo { constructor(val: MyObject) { /* ... */ } }

别名泛型

type MyObject<A, B, C> = {
      property: A,
      method(val: B): C,
    };

别名泛型是参数化的,也就是你用了之后, 你声明的全部参数 你所有都要传

// @flow
    type MyObject<A, B, C> = {
      foo: A,
      bar: B,
      baz: C,
    };

    var val: MyObject<number, boolean, string> = {
      foo: 1,
      bar: true,
      baz: 'three',
    };

不透明的类型别名(opaque type aliases)

经过类型系统的增强抽象

不透明类型别名是不容许访问定义在文件以外的的基础类型的类型别名.

opaque type ID = string;  // 一个新的关键字 而且这是声明一个不透明类型别名的语法

不透明类型别名能够复用

// @flow
    // 在这个例子,我理解的是 外部只能访问到这个文件的ID 类型, 并不能访问到这个文件里面的string 基础类型. 这就是不透明的类型别名
    opaque type ID = string;

    function identity(x: ID): ID {
      return x;
    }
    export type {ID};

你能够可选的加一个子类型约束 在一个 不透明的类型别名的类型后面

opaque type Alias: SuperType = Type;

任何类型均可以做为父类型 或者 不透明的类型别名 的类型

opaque type StringAlias = string;
    opaque type ObjectAlias = {
      property: string,
      method(): number,
    };
    opaque type UnionAlias = 1 | 2 | 3;
    opaque type AliasAlias: ObjectAlias = ObjectAlias;
    opaque type VeryOpaque: AliasAlias = ObjectAlias;

不透明别名类型 的类型检查

在文件内部

在文件内部跟正常的类型别名同样

//@flow
    opaque type NumberAlias = number;

    (0: NumberAlias);

    function add(x: NumberAlias, y: NumberAlias): NumberAlias {
        return x + y;
    }
    function toNumberAlias(x: number): NumberAlias { return x; }
    function toNumber(x: NumberAlias): number { return x; }

在文件外部

当你inport 一个 不透明的类型别是时候,他会隐藏基础类型

exports.js

export opaque type NumberAlias = number;

imports.js

import type {NumberAlias} from './exports';

    (0: NumberAlias) // Error: 0 is not a NumberAlias!

    function convert(x: NumberAlias): number {
      return x; // Error: x is not a number!
    }

子类型约束(subTyping Constraints)

当你添加一个子 类型约束在一个不透明的类型别名上时, 咱们容许不透明类型在被定义文件的外部被用做父类型

exports.js

export opaque type ID: string = string;

imports.js

import type {ID} from './exports';

    function formatID(x: ID): string {
        return "ID: " + x; // Ok! IDs are strings.
    }

    function toID(x: string): ID {
        return x; // Error: strings are not IDs.
    }

当你建立一个拥有子类型约束的 不透明类型别名, 这个类型在类型中的位置必定要是这个类型的子类型在父类中的位置 (这里的概念应该是跟泛型的概念差很少, 不相关的类型不能够强制转换)

//@flow
    opaque type Bad: string = number; // Error: number is not a subtype of string
    opaque type Good: {x: string} = {x: string, y: number};

泛型

不透明类型别名 有他们本身的泛型, 可是他们跟正常的泛型是差很少的

// @flow
    opaque type MyObject<A, B, C>: { foo: A, bar: B } = {
      foo: A,
      bar: B,
      baz: C,
    };

    var val: MyObject<number, boolean, string> = {
      foo: 1,
      bar: true,
      baz: 'three',
    };

接口类型 (interface Types)

接口可使一些拥有相同方法的类归为一类

// @flow
    interface Serializable {
      serialize(): string;
    }

    class Foo {
      serialize() { return '[Foo]'; }
    }

    class Bar {
      serialize() { return '[Bar]'; }
    }

    const foo: Serializable = new Foo(); // Works!
    const bar: Serializable = new Bar(); // Works!

若是你怕出错, 你能够手动的 使用 implements 告诉flow 哪些类实现了哪些接口,这能够预防你修改class 的时候出现错误

// @flow
    interface Serializable {
      serialize(): string;
    }

    class Foo implements Serializable {
      serialize() { return '[Foo]'; } // Works!
    }

    class Bar implements Serializable {
      // $ExpectError
      serialize() { return 42; } // Error! // 不能返回一个number
    }

不要忘记了接口能够同时实现多个

接口的属性也是能够可选的

interface MyInterface {
      property?: string;
    }

接口跟maps 联合

interface MyInterface {
      [key: string]: number;
    }

接口泛型

interface MyInterface<A, B, C> {
      property: A;
      method(val: B): C;
    }

规矩还在,泛型你用了几个 ,你使用的时候 就要传递几个参数

// @flow
    interface MyInterface<A, B, C> {
      foo: A;
      bar: B;
      baz: C;
    }

    var val: MyInterface<number, boolean, string> = {
      foo: 1,
      bar: true,
      baz: 'three',
    };

接口属性的 只读,与只写

接口属性默认是不可变的, 可是你能够添加修饰符让他们变成 covariant只读或者Contravariance 只写;(关于不可变想了解的请看这里)

interface MyInterface {
      +covariant: number;     // read-only 只读 不能修改
      -contravariant: number; // write-only 只能修改, 不能读取
    }

混合只读

interface MyInterface {
      +readOnly: number | string;
    }

容许指定多个类型

// @flow
    // $ExpectError
    interface Invariant {  property: number | string }
    interface Covariant { +readOnly: number | string }

    var value1: Invariant = { property: 42 }; // Error!
    var value2: Covariant = { readOnly: 42 }; // Works!

协变(covariant) 属性 一般是只读的,他比正常的属性更有用

// @flow
    interface Invariant {  property: number | string }
    interface Covariant { +readOnly: number | string }

    function method1(value: Invariant) {
      value.property;        // Works!
      value.property = 3.14; // Works!
    }

    function method2(value: Covariant) {
      value.readOnly;        // Works!
      // $ExpectError
      value.readOnly = 3.14; // Error!
    }

contravariant 逆变 只写属性 容许你传递更少的类型

// @flow
    interface Invariant     {  property: number }
    interface Contravariant { -writeOnly: number }

    var numberOrString = Math.random() > 0.5 ? 42 : 'forty-two';

    // $ExpectError
    var value1: Invariant     = { property: numberOrString };  // Error!
    var value2: Contravariant = { writeOnly: numberOrString }; // Works! 能够看到 上面声明了 number 但是这个numberOrString 有两种返回值, 他只能匹配一种 他野是能够传递的

一般比正常的属性更有用

interface Invariant     {   property: number }
    interface Contravariant { -writeOnly: number }

    function method1(value: Invariant) {
      value.property;        // Works!
      value.property = 3.14; // Works!
    }

    function method2(value: Contravariant) {
      // $ExpectError
      value.writeOnly;        // Error!
      value.writeOnly = 3.14; // Works!
    }

联盟类型 (union types)

类型的值多是不少类型之一

使用 | 分开

Type1 | Type2 | ... | TypeN

能够竖直写

type Foo =
      | Type1
      | Type2
      | ...
      | TypeN

联盟类型能够组合

type Numbers = 1 | 2;
    type Colors = 'red' | 'blue'

    type Fish = Numbers | Colors;

联盟类型请求一个,可是全部的都要处理

当你调用一个要接受联盟类型的函数的时候,你必定要传入一个在联盟类型中的类型,可是在函数里面你要处理全部的类型.

// @flow
    // $ExpectError
    function toStringPrimitives(value: number | boolean | string): string { // Error!
      if (typeof value === 'number') {
        return String(value);
      } else if (typeof value === 'boolean') {
        return String(value);
      }
      // 注意这个函数会报错是由于 你用了if 条件语句 并无在全部的状况中返回值, 若是返回了undefined 那么就不符合 string 类型,因此就报错了
    }

联盟改进

这里是上面演示的说明,可使用 typeof 关键字来应对逐一的类型

// @flow
    function toStringPrimitives(value: number | boolean | string) {
      if (typeof value === 'number') {
        return value.toLocaleString([], { maximumSignificantDigits: 3 }); // Works!
      }
      // ...
    }

脱节联盟 (disjoint Unions)

概念就不说了,难懂来看一下例子

想象咱们有一个处理发送了请求以后响应的函数,当请求成功你那个的时候,咱们获得一个对象,这个对象有 一个 success 属性 值为true 还有一个值咱们须要更新的值, value

{ success: true, value: false };

当请求失败的时候,咱们获得一个对象这个对象有一个 success 属性 值为false,和一个 error 属性,定义了一个错误.

{ success: false, error: 'Bad request' };

咱们能够尝试用一个对象去描述这两个对象, 然而咱们很快就发生了一个问题, 就是咱们知道一个属性的存在与否(value 或者 error ) 取决于success(由于success为 true 那么 value才会存在) 可是 Flow 不知道.

// @flow
    type Response = {
      success: boolean,
      value?: boolean,
      error?: string
    };

    function handleResponse(response: Response) {
      if (response.success) {
        // $ExpectError
        var value: boolean = response.value; // Error!
      } else {
        // $ExpectError
        var error: string = response.error; // Error!
      }
    }

取而代之,若是咱们建立一个两个对象类型的联盟类型,Flow 会知道基于success 属性 咱们会使用哪一个对象

// @flow
    type Success = { success: true, value: boolean };
    type Failed  = { success: false, error: string };

    type Response = Success | Failed; (这就是脱节联盟)

    function handleResponse(response: Response) {
      if (response.success) {
        var value: boolean = response.value; // Works!
      } else {
        var error: string = response.error; // Works!
      }
    }

脱节联盟与精确类型仪器使用

脱节连门要求你使用单一的属性去区分每一个对象类型,你不能用两个不一样的属性,去区分两个不一样的类型

// @flow
    type Success = { success: true, value: boolean };
    type Failed  = { error: true, message: string };

    function handleResponse(response:  Success | Failed) {
      if (response.success) {
        // $ExpectError
        var value: boolean = response.value; // Error!
      }
    }
    // 不懂的跟上面的对比一下, 两个对象必需要有一个属性是相同的

然而 你能够用精确对象类型

// @flow
    type Success = {| success: true, value: boolean |};
    type Failed  = {| error: true, message: string |};
    // 精确的也就是说 不能够扩展对象, 他该是哪一个就是哪一个 不存在混乱

    type Response = Success | Failed;

    function handleResponse(response: Response) {
      if (response.success) {
        var value: boolean = response.value;
      } else {
        var message: string = response.message;
      }
    }

交叉类型(intersection types)

全部不一样类型的类型值

// @flow
    type A = { a: number };
    type B = { b: boolean };
    type C = { c: string };

    function method(value: A & B & C) {
      // ...
    }

    // $ExpectError
    method({ a: 1 }); // Error!
    // $ExpectError
    method({ a: 1, b: true }); // Error!
    method({ a: 1, b: true, c: 'three' }); // Works!

能够把上面的连门类型理解为或 把交叉类型理解为& 语法都是同样的

type Foo =
      & Type1
      & Type2
      & ...
      & TypeN
    type Foo = Type1 & Type2;
    type Bar = Type3 & Type4;

    type Baz = Foo & Bar;

咱们在函数中和联盟函数相反, 咱们不如传入全部的类型,可是在函数里面咱们只须要作处理一种状况就OK

// @flow
    type A = { a: number };
    type B = { b: boolean };
    type C = { c: string };

    function method(value: A & B & C) {
      var a: A = value;
      var b: B = value;
      var c: C = value;
    }

不可能的交叉类型

你总不能一个值 是数字的同时又是字符串吧

// @flow
    type NumberAndString = number & string;

    function method(value: NumberAndString) {
      // ...
    }

    // $ExpectError
    method(3.14); // Error!
    // $ExpectError
    method('hi'); // Error!

交叉对象类型

当你建立一个交叉对象类型时,你是在合并了他们全部的属性在一个对象上

// @flow
    type One = { foo: number };
    type Two = { bar: boolean };

    type Both = One & Two;

    var value: Both = {
      foo: 1,
      bar: true
    };

若是声明的属性类型相同, 就至关于你声明了一个 交叉类型的属性

typeof Types (这个很差翻译 由于 typeof 是js中的一个关键字,在此我就不翻译这个了)

js有一个typeof 关键字,他会返回一个字符串说明

然而他是有限制的,typeof 对象 数组 null 都是 object

因此在flow中, 他把这个关键字重载了

// @flow
    let num1 = 42;
    let num2: typeof num1 = 3.14;     // Works!
    // $ExpectError
    let num3: typeof num1 = 'world';  // Error!

    let bool1 = true;
    let bool2: typeof bool1 = false;  // Works!
    // $ExpectError
    let bool3: typeof bool1 = 42;     // Error!

    let str1 = 'hello';
    let str2: typeof str1 = 'world'; // Works!
    // $ExpectError
    let str3: typeof str1 = false;   // Error!

你能够typeof 任何值

// @flow
    let obj1 = { foo: 1, bar: true, baz: 'three' };
    let obj2: typeof obj1 = { foo: 42, bar: false, baz: 'hello' };

    let arr1 = [1, 2, 3];
    let arr2: typeof arr1 = [3, 2, 1];

引用类型的 typeof 继承行为

你能够用typeof 的返回值做为一个类型

可是若是你typeof 一个指定了字面量类型的 变量, 那么那个类型就是字面量的值了

// @flow
    let num1: 42 = 42;
    // $ExpectError
    let num2: typeof num1 = 3.14;    // Error!
    // 看这里 num1 的type 指定了是 42 那么 typeof num1 不会返回number 会返回 42 因此3.14 不符合

    let bool1: true = true;
    // $ExpectError
    let bool2: typeof bool1 = false; // Error!

    let str1: 'hello' = 'hello';
    // $ExpectError
    let str2: typeof str1 = 'world'; // Error!

其余类型的 typeof 继承行为

// @flow
    class MyClass {
      method(val: number) { /* ... */ }
    }

    class YourClass {
      method(val: number) { /* ... */ }
    }

    // $ExpectError
    let test1: typeof MyClass = YourClass; // Error!
    let test2: typeof MyClass = MyClass;   // Works!
    // 看这里 es6 的类并非一种类型, 只是一种语法糖而已,内部机制仍是原型链, 因此 typeof MyClass 不会等于YourClass

镶嵌表达式类型(type casting expression)

把一个值镶嵌到不一样的类型

有时不使用函数和变量去声明一个类型是颇有用的,因此flow 支持多种方式去干这个事情(声明一个类型)

语法

(value: Type)

这个表达式能够出如今表达式能出现的任何地方

let val = (value: Type);
    let obj = { prop: (value: Type) };
    let arr = ([(value: Type), (value: Type)]: Array<Type>);

也能够这样写

(2 + 2: number);

类型断言

// @flow
    let value = 42;
    // 这个的做用就是把变量 嵌入到一个类型中去
    (value: 42);     // Works!
    (value: number); // Works!
    (value: string); // Error!

类型嵌入

这个表达式是由返回值的,若是你接收了这个返回值,你会获得一个新的类型

// @flow
    let value = 42;

    (value: 42);     // Works!
    (value: number); // Works!

    let newValue = (value: number);

    // $ExpectError
    (newValue: 42);     // Error!
    (newValue: number); // Works!

经过 any 去转换类型

let value = 42;

    (value: number); // Works!
    // $ExpectError
    (value: string); // Error!
    // 这里先把value 变成any 再变成string
    let newValue = ((value: any): string);

    // $ExpectError
    (newValue: number); // Error!
    // 生效了
    (newValue: string); // Works!

可是合适不安全且不推荐的,可是有时候他颇有用

经过类型断言来进行类型检查

如果你想检查一个对象的类型,你不能直接 typeof 你得先用 断言表达式去转换而后再用typeof 去检查

想这样:

function clone(obj: { [key: string]: mixed }) {
      const cloneobj = {};
      Object.keys(obj).forEach(key => {
        cloneobj[key] = obj[key];
      });

      return ((cloneobj: any): typeof obj);
    }

    const obj = clone({foo: 1})
    (obj.foo: 1) // 出错!

function clone(obj) {
      (obj: { [key: string]: mixed });
      const cloneobj = {};
      Object.keys(obj).forEach(key => {
        cloneobj[key] = obj[key];
      });

      return ((cloneobj: any): typeof obj);
    }

    const obj = clone({foo: 1})
    (obj.foo: 1) // ok!

工具类型

flow 提供了一系列的 工具类型, 以便于再一些常见场景使用

详情看这里

模块类型

上面由相似的, 就是一个export 一个 import

注释类型

感受没多大用处, 能够作一些标记,这个能够在不经过flow 编译的状况下直接使用在js文件上

// @flow

    /*::
    type MyAlias = {
      foo: number,
      bar: boolean,
      baz: string,
    };
    */

    function method(value /*: MyAlias */) /*: boolean */ {
      return value.bar;
    }

    method({ foo: 1, bar: true, baz: ["oops"] });

看完能看懂全部flow 代码了吧...

相关文章
相关标签/搜索