ホームページ > ウェブフロントエンド > jsチュートリアル > JavaScript の静的型はフローの使用状況をどのように解析するか (詳細)

JavaScript の静的型はフローの使用状況をどのように解析するか (詳細)

不言
リリース: 2018-09-14 15:46:09
オリジナル
1727 人が閲覧しました

この記事の内容は、JavaScript の静的型を使用してフローを解析する方法に関するものです (詳細)。必要な方は参考にしていただければ幸いです。

原因

BaiduとGoogleで検索しても、フローに関する中国語のドキュメントは見つかりませんでした。これは明らかに中国にとって不親切です。フローは通常は使用されませんが、一般的に使用されます。ユーザーの利便性を考慮したフレームワークは正確に使用でき、多くの謎のバグを回避できます。そのようなものはないので、まず型アノテーションの部分を翻訳してから、たくさん検索するつもりです。

フロー タイプのアノテーション

タイプにアノテーションが付けられていない場合、フローは機能しません。そのため、フロー タイプにアノテーションを付ける方法を見てみましょう。 // このアノテーションはありません#。

##元の型

JavaScript には合計 6 つのプリミティブ データ型があります。

    ##ブール値
  • 文字列
  • Numbers
  • null
  • 未定義 (フロー タイプでは無効)
  • シンボル (ECMAScript 2015 の新機能、フローではまだサポートされていません) フローはシンボルをサポートしていません
  • 元の型は 2 つの型に分けられます。1 つはリテラル、もう 1 つはリテラルです。もう 1 つは、3 や Number( 3) など、パッケージ化されています。
たとえば、以下に示すように、

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

method(3.14, "hello", true);
ログイン後にコピー

booleans

flow が認識できるものを除き、リテラル .booleans のみを渡すことができます。 !!x と Boolean(0) の明示的な型変換ブール値

// @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
    "foo" + "foo"; // Works!
    "foo" + 42;    // Works!
    "foo" + {};    // Error!
    "foo" + [];    // Error!
ログイン後にコピー

これを使用したい場合は、明示的に

    // @flow
    "foo" + String({});     // Works!
    "foo" + [].toString();  // Works!
    "" + JSON.stringify({}) // Works!
ログイン後にコピー

null と unknown を変換する必要があります

フローでは、未定義は void です

    // @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!
ログイン後にコピー

この値は未定義にすることもできますが、 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!
ログイン後にコピー
混合タイプ

複数のタイプに一致させることができます

    function stringifyBasicValue(value: string | number) {
      return '' + value;
    }
ログイン後にコピー

You Java のジェネリックスと同様に型をマークできます (違いはあります)。次の例は、関数によって返される型が関数に渡される型と同じであることを示しています。このように受け入れられます 任意の型のパラメータ

function identity<T>(value: T): T {
  return value;
}
ログイン後にコピー
mixedを使用する場合、任意の型を渡すことができますが、それを返すときにそれがどのような型であるかを知っておく必要があります。そうしないと、エラーが報告されます

    function getTypeOf(value: mixed): string {
      return typeof value;
    }
ログイン後にコピー
Any 型 (任意の型)

any とmixed を混同しないでください。型チェックをスキップしたい場合は、any を使用してください。

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

    stringify("foo");
ログイン後にコピー
2 つの状況がある限り、使用できます。 use any

新しいフロー タイプ チェックが古いコードに追加され、他のタイプを使用すると多くのエラーが発生します

  1. 明らかにコードが型チェックに合格できないことを知ってください。

  2. any のリークを避ける

    渡されたパラメータのいずれかを宣言すると、返されるパラメータも any になります。この状況を回避する必要があります。
  3.     // @flow
        function add(one: any, two: any): number {
          return one + two;
        }
    
        add(1, 2);     // Works.
        add("1", "2"); // Works.
        add({}, []);   // Works.
    ログイン後にコピー
おそらく type

は、使用できる上記の型ですか? 疑問符は、オプションの type

変数としてマークされています。 type )

var - 変数を宣言、選択的代入

  • #let - ブロックレベル変数を宣言、選択的補助

  • const - ブロックレベル変数を宣言し、値を割り当てます。再度割り当てることはできません。

  • #フローは 2 つのグループに分割され、1 つのグループは let とvar は再度割り当てることができ、他のグループは const であり、再度割り当てることはできません。

    const
const は、割り当てた型を注入することも、手動で型を指定することもできます。
    // @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;
ログイン後にコピー

let と var

上記と同じように、これら 2 つはタイプを自動的に注入することもできます

ただし、タイプを自動的に注入すると、変更されたタイプを再割り当てするときにエラーは発生しません

If ステートメント、関数、およびその他の条件付きコードは、それらがどのような型であるかを正確に示すことができるため、フロー チェックやエラー レポートを回避できます

    // @flow
    const foo /* : number */ = 1;
    const bar: number = 2;
ログイン後にコピー

上記の使用法は避けるようにしてください (個人的な推奨事項)

関数の種類 (関数の種類)

関数の用途は 2 つだけです。パラメータまたは戻り値のいずれかです。

        // @flow
        let foo = 42;

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

        mutate();

        // $ExpectError
        let isString: string = foo; // Error!
ログイン後にコピー

宣言関数

上記と同じです

アロー関数

    // @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!
ログイン後にコピー

関数の戻り値

    function method(...args: Array<number>) {
      // ...  使类似java 泛型的 样子 来声明他的类型
    }
ログイン後にコピー

関数のこれ

これをコメントする必要はありません。フローはコンテキストを自動的に検出して this のタイプを決定します。

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

ただし、以下ではこの状況では、フローはエラーを報告します

    function method() {
      return this;
    }

    var num: number = method.call(42);
    // $ExpectError
    var str: string = method.call(42);
ログイン後にコピー

truey で %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

数组的访问时不安全的

    // @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

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

    // @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 代码了吧...

相关推荐:

Flow之一个新的Javascript静态类型检查器_javascript技巧

JavaScript静态类型检查工具FLOW简介_基础知识

以上がJavaScript の静的型はフローの使用状況をどのように解析するか (詳細)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート