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 代碼了吧...

相關文章
相關標籤/搜索