>웹 프론트엔드 >JS 튜토리얼 >javascript 정적 유형은 흐름 사용법을 어떻게 구문 분석합니까(세부 사항)

javascript 정적 유형은 흐름 사용법을 어떻게 구문 분석합니까(세부 사항)

不言
不言원래의
2018-09-14 15:46:091724검색

이 기사의 내용은 JavaScript 정적 유형을 사용하여 흐름을 구문 분석하는 방법(세부 사항)입니다. 필요한 친구가 참고할 수 있기를 바랍니다.

원인

바이두와 구글에서 검색해봐도 플로우에 대한 중국어 문서는 찾을 수 없었습니다. 이는 분명히 플로우가 일반적으로 사용되지는 않지만 사용자가 프레임워크를 사용할 수 있도록 프레임워크에서 일반적으로 사용되는 것입니다. 알 수 없는 버그가 많아서 번역해보겠습니다. 먼저 Type Annotation 부분을 번역해서 많이 설치할 예정입니다.

flow type Annotations

annotated 이때는 flow가 동작하지 않으니 어떻게 flow type을 annotate할 수 있는지 살펴보자. // 이 Annotation

original type

javascript에는 총 6가지의 원시 데이터 타입이 있다.

  • Booleans

  • Strings

  • Numbers

  • null

  • undefine(Flow 유형에서는 무효)

  • Symbols(ECMAScript 2015의 새로운 기능, 아직 지원되지 않음) in Flow) 흐름은 기호를 지원하지 않습니다.

원래 유형은 두 가지로 나누어집니다. 하나는 리터럴이고 다른 하나는 패키징됩니다. 3과 Number(3);
예를 들어 아래와 같이 literals.booleans만 전달할 수 있습니다. Except

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

method(3.14, "hello", true);

booleans

flow는!!x를 인식할 수 있으며 Boolean(0)의 명확한 유형 변환은 boolean

// @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 및 정의되지 않음

을 변환해야 합니다. undefine 흐름에서는 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는 아직

Literal type

을 지원하지 않습니다. 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!

혼합 유형.

여러 A 유형과 일치할 수 있습니다

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

Java의 제네릭과 같은 유형을 표시할 수 있습니다(차이 있음). 다음 예에서는 함수에서 반환된 유형이 함수에 전달된 유형과 동일함을 보여줍니다.

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

You 다음과 같은 함수를 표시할 수 있습니다. 모든 유형의 매개변수를 허용할 수 있습니다

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

혼합을 사용하는 경우 어떤 유형이든 전달할 수 있지만 반환할 때 어떤 유형인지 알아야 합니다. 그렇지 않으면 오류가 보고됩니다

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

    stringify("foo");

Any 유형(모든 유형)

any와 혼합하여 혼동하지 마십시오. 유형 검사를 건너뛰려면 any

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

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

를 사용하십시오. 두 가지 상황이 있는 한 any를 사용할 수 있습니다

    flow 유형 검사를 추가하세요 이전 코드를 사용하고 다른 유형을 사용하면 많은 오류가 발생합니다
  1. 당신의 코드가 유형 검사를 통과할 수 없다는 것을 확실히 알고 있다면
  2. 누출을 피하세요

전달된 매개변수 중 하나를 선언할 때, 반환하는 매개변수도 모두 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;

아마도 유형

위에 언급된 매개변수인가요? 물음표는 이를 선택적 유형

변수 유형

으로 표시합니다.

    var - 변수 선언, 선택적 할당
  • let - 블록 수준 변수 선언, 선택적 보조
  • const - 블록 수준 변수 선언, 값 할당, 다시 할당할 수 없음
  • in flow 두 그룹으로 나누어 하나는 다시 할당할 수 있는 let과 var이고, 다른 하나는 다시 할당할 수 없는 const입니다.

const

const 할당한 유형을 주입하거나 직접 유형을 직접 지정할 수 있습니다.

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

let 및 var

위와 마찬가지로 이 두 가지도 자동으로 유형을 주입할 수 있습니다

하지만 자동으로 유형을 주입하면 수정된 유형을 다시 할당할 때 오류가 발생하지 않습니다

If 문, 함수 및 기타 조건부 코드, 어떤 타입인지 정확하게 표시할 수 있어 흐름 체크나 오류 보고를 피할 수 있습니다

        // @flow
        let foo = 42;

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

        mutate();

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

위와 같은 사용은 피해주세요(개인 추천)

함수 종류(함수 종류)

방법은 2가지뿐입니다 함수나 매개변수 중 하나를 사용하세요. 반환값

    // @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 泛型的 样子 来声明他的类型
    }

함수 return value

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

Function This

이 항목에 주석을 달 필요는 없습니다. 흐름은 this

    function method() {
      return this;
    }

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

유형을 결정하기 위해 자동으로 컨텍스트를 감지합니다. 그러나 다음의 경우 흐름은 오류를 보고합니다

    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 '';
    }

You 이 방법으로 위의 문제를 해결할 수 있으며, 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

数组的访问时不安全的

    // @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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.