资讯专栏INFORMATION COLUMN

js静态类型解析flow用法

quietin / 1464人阅读

摘要:起因遍寻百度没发现的中文文档这对国内显然是不友好的虽说平时用不着但是一般框架都会用一下以便用户可以准确的使用框架可以避免很多谜一样的既然没有那我就来翻译一下咯计划先翻译类型注释部分安装的一搜一大把类型注释当你的类型不注释的时候就不起作用了来

起因

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

flow 类型注释

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

原始类型

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

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 除外

</>复制代码

  1. // @flow
  2. function method(x: number, y: string, z: boolean) {
  3. // ...
  4. }
  5. method(3.14, "hello", true);
booleans

flow 可以识别 !!x 和 Boolean(0) 的明确类型转换的boolean

</>复制代码

  1. // @flow
  2. function acceptsBoolean(value: boolean) {
  3. // ...
  4. }
  5. acceptsBoolean(0); // Error! 错误
  6. acceptsBoolean(Boolean(0)); // Works! OK
  7. acceptsBoolean(!!0); // Works! OK
number

数字就很明确了

</>复制代码

  1. // @flow
  2. function acceptsNumber(value: number) {
  3. // ...
  4. }
  5. acceptsNumber(42); // Works!
  6. acceptsNumber(3.14); // Works!
  7. acceptsNumber(NaN); // Works!
  8. acceptsNumber(Infinity); // Works!
  9. acceptsNumber("foo"); // Error!
string

</>复制代码

  1. // @flow
  2. function acceptsString(value: string) {
  3. // ...
  4. }
  5. acceptsString("foo"); // Works!
  6. acceptsString(false); // Error!

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

</>复制代码

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

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

</>复制代码

  1. // @flow
  2. "foo" + "foo"; // Works!
  3. "foo" + 42; // Works!
  4. "foo" + {}; // Error!
  5. "foo" + []; // Error!

如果你要使用, 必须要明确转换

</>复制代码

  1. // @flow
  2. "foo" + String({}); // Works!
  3. "foo" + [].toString(); // Works!
  4. "" + JSON.stringify({}) // Works!
null 和 undefined

在flow中 undefined 是 void

</>复制代码

  1. // @flow
  2. function acceptsNull(value: null) {
  3. /* ... */
  4. }
  5. function acceptsUndefined(value: void) {
  6. /* ... */
  7. }
  8. acceptsNull(null); // Works!
  9. acceptsNull(undefined); // Error!
  10. acceptsUndefined(null); // Error!
  11. acceptsUndefined(undefined); // Works!
可能的类型

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

</>复制代码

  1. // @flow
  2. function acceptsMaybeString(value: ?string) {
  3. // ...
  4. }
  5. acceptsMaybeString("bar"); // Works!
  6. acceptsMaybeString(undefined); // Works!
  7. acceptsMaybeString(null); // Works!
  8. acceptsMaybeString(); // Works!
对象属性选项

你可以用一个问号来表示该对象的某个属性是可有可无的

</>复制代码

  1. // @flow
  2. function acceptsObject(value: { foo?: string }) {
  3. // ...
  4. }
  5. acceptsObject({ foo: "bar" }); // Works!
  6. acceptsObject({ foo: undefined }); // Works!
  7. acceptsObject({ foo: null }); // Error!
  8. acceptsObject({}); // Works!

这个值可以是undefined 但是 他不能是null

函数参数的选项

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

</>复制代码

  1. // @flow
  2. function acceptsOptionalString(value?: string) {
  3. // ...
  4. }
  5. acceptsOptionalString("bar"); // Works!
  6. acceptsOptionalString(undefined); // Works!
  7. acceptsOptionalString(null); // Error!
  8. acceptsOptionalString(); // Works!
函数的默认参数

es5 的新特性

</>复制代码

  1. // @flow
  2. function acceptsOptionalString(value: string = "foo") {
  3. // ...
  4. }
  5. acceptsOptionalString("bar"); // Works!
  6. acceptsOptionalString(undefined); // Works!
  7. acceptsOptionalString(null); // Error!
  8. acceptsOptionalString(); // Works!
symbol

flow未支持

字面类型

flow 不止可以指定类型, 他还可以指定某个特定的值. 非常牛掰

如:

</>复制代码

  1. // @flow
  2. function acceptsTwo(value: 2) {
  3. // ...
  4. }
  5. acceptsTwo(2); // Works!
  6. // $ExpectError
  7. acceptsTwo(3); // Error!
  8. // $ExpectError
  9. acceptsTwo("2"); // Error!

如:

</>复制代码

  1. // @flow
  2. function getColor(name: "success" | "warning" | "danger") {
  3. switch (name) {
  4. case "success" : return "green";
  5. case "warning" : return "yellow";
  6. case "danger" : return "red";
  7. }
  8. }
  9. getColor("success"); // Works!
  10. getColor("danger"); // Works!
  11. // $ExpectError
  12. getColor("error"); // Error!
杂交类型 (mixed types)

你可以匹配多个类型

</>复制代码

  1. function stringifyBasicValue(value: string | number) {
  2. return "" + value;
  3. }

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

</>复制代码

  1. function identity(value: T): T {
  2. return value;
  3. }

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

</>复制代码

  1. function getTypeOf(value: mixed): string {
  2. return typeof value;
  3. }

当你使用 mixed 时候, 虽然你可以传进任何类型, 但是你返回的时候 必须要明确他是什么类型, 不然就报错

</>复制代码

  1. // @flow
  2. function stringify(value: mixed) {
  3. // $ExpectError
  4. return "" + value; // Error!
  5. }
  6. stringify("foo");
任何类型(any type)

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

</>复制代码

  1. // @flow
  2. function add(one: any, two: any): number {
  3. return one + two;
  4. }
  5. add(1, 2); // Works.
  6. add("1", "2"); // Works.
  7. add({}, []); // Works.

只要两种情况,可以使用 any

旧代码 新增flow 类型检查,并且 用其他类型的会引起大量错误

当你明确的知道你的代码不能通过类型检查的时候,

避免泄漏any

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

</>复制代码

  1. // @flow
  2. function fn(obj: any) /* (:number) */ {
  3. let foo: number = obj.foo; // 这句才是重点, 切断 any
  4. let bar /* (:number) */ = foo * 2;
  5. return bar;
  6. }
  7. let bar /* (:number) */ = fn({ foo: 2 });
  8. let baz /* (:string) */ = "baz:" + bar;
可能类型 (maybe type)

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

变量类型 (variable type)

var - 声明一个变量,选择性赋值

let - 声明一个块级变量,选择性辅助

const - 声明一个块级变量,并赋值,且不能再次赋值

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

const

const 可以注入你赋值的类型, 或者你自己手动的指定类型

</>复制代码

  1. // @flow
  2. const foo /* : number */ = 1;
  3. const bar: number = 2;
let 和 var

跟上面一样, 这两个也可以自动的注入类型

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

如果语句,函数,和其他的条件代码,可以精确的指出他是什么类型,那么就可以避免flow 的检查 不然就报错

</>复制代码

  1. // @flow
  2. let foo = 42;
  3. function mutate() {
  4. foo = true;
  5. foo = "hello";
  6. }
  7. mutate();
  8. // $ExpectError
  9. let isString: string = foo; // Error!

尽量避免上面的用法(个人推荐)

函数类型(function type)

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

</>复制代码

  1. // @flow
  2. function concat(a: string, b: string): string {
  3. return a + b;
  4. }
  5. concat("foo", "bar"); // Works!
  6. // $ExpectError
  7. concat(true, false); // Error!
声明函数

同上

箭头函数

</>复制代码

  1. (str: string, bool?: boolean, ...nums: Array) => void
带回调的箭头函数

</>复制代码

  1. function method(callback: (error: Error | null, value: string | null) => void) {
  2. // 上面表明, 这和函数接受的参数 只能是 error null 和 string 然后染回一个 undefined
  3. }
可选参数

</>复制代码

  1. // @flow
  2. function method(optionalValue?: string) {
  3. // ...
  4. }
  5. method(); // Works.
  6. method(undefined); // Works.
  7. method("string"); // Works.
  8. // $ExpectError
  9. method(null); // Error!
剩余参数的用法 (rest parameter)

</>复制代码

  1. function method(...args: Array) {
  2. // ... 使类似java 泛型的 样子 来声明他的类型
  3. }
函数返回值

</>复制代码

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

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

</>复制代码

  1. function method() {
  2. return this;
  3. }
  4. var num: number = method.call(42);
  5. // $ExpectError
  6. var str: string = method.call(42);

但是下面这种情况,flow 就会报错

</>复制代码

  1. function truthy(a, b): boolean {
  2. return a && b;
  3. }
  4. function concat(a: ?string, b: ?string): string {
  5. if (truthy(a, b)) {
  6. // $ExpectError 问题出现再truthy 上 可能是 经过了隐式的类型转换
  7. return a + b;
  8. }
  9. return "";
  10. }

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

</>复制代码

  1. function truthy(a, b): boolean %checks {
  2. return !!a && !!b;
  3. }
  4. function concat(a: ?string, b: ?string): string {
  5. if (truthy(a, b)) {
  6. return a + b;
  7. }
  8. return "";
  9. }

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

</>复制代码

  1. function method(func: Function) {
  2. func(1, 2); // Works.
  3. func("1", "2"); // Works.
  4. func({}, []); // Works.
  5. }
  6. method(function(a: number, b: number) {
  7. // ...
  8. });
对象类型 对象类型语法

</>复制代码

  1. // @flow
  2. var obj1: { foo: boolean } = { foo: true };
  3. var obj2: {
  4. foo: number,
  5. bar: boolean,
  6. baz: string,
  7. } = {
  8. foo: 1,
  9. bar: true,
  10. baz: "three",
  11. };
可选的对象属性

使用了flow, 对象不能访问不存再的属性, 以前是返回undefined 现在访问报错,如果想知道 访问并且赋值 会不会报错,(我建议你自己试一下)

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

</>复制代码

  1. // @flow
  2. var obj: { foo?: boolean } = {};
  3. obj.foo = true; // Works!
  4. // $ExpectError
  5. obj.foo = "hello"; // Error!

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

密封对象 (seald objects)

密封对象的概念不懂的 可以去了解一下, 就是这个对象不可以修改,但是引用的对象还是可以修改的; 在flow中 这种对象知道所有你声明的属性的值的类型

</>复制代码

  1. // @flow
  2. var obj = {
  3. foo: 1,
  4. bar: true,
  5. baz: "three"
  6. };
  7. var foo: number = obj.foo; // Works!
  8. var bar: boolean = obj.bar; // Works!
  9. // $ExpectError
  10. var baz: null = obj.baz; // Error!
  11. var bat: string = obj.bat; // Error!

而且flow 不允许你往这种对象上面添加新的属性, 不然报错

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

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

</>复制代码

  1. // @flow
  2. var obj = {};
  3. if (Math.random()) obj.prop = true;
  4. else obj.prop = "hello";
  5. // $ExpectError
  6. var val1: boolean = obj.prop; // Error!
  7. // $ExpectError
  8. var val2: string = obj.prop; // Error!
  9. var val3: boolean | string = obj.prop; // Works!
普通对象的非确定属性的类型是不安全的

</>复制代码

  1. var obj = {};
  2. obj.foo = 1;
  3. obj.bar = true;
  4. var foo: number = obj.foo; // Works!
  5. var bar: boolean = obj.bar; // Works!
  6. var baz: string = obj.baz; // Works? // 问题在这里 这里的baz 是不存在的属性, 把他定位string 并且给他一个undefined 是可行的, 但是这部安全, 避免使用
额外对象类型 (extra object type)

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

</>复制代码

  1. // @flow
  2. function method(obj: { foo: string }) {
  3. // ...
  4. }
  5. method({
  6. foo: "test", // Works!
  7. bar: 42 // Works!
  8. });

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

</>复制代码

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

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

</>复制代码

  1. // @flow
  2. type FooT = {| foo: string |};
  3. type BarT = {| bar: number |};
  4. type FooBarFailT = FooT & BarT; // 通过这个& 操作可以把两种情况合并,匹配到 foo 和 bar 同时都有的对象 而且类型必须跟声明的一样
  5. type FooBarT = {| ...FooT, ...BarT |};
  6. const fooBarFail: FooBarFailT = { foo: "123", bar: 12 }; // Error!
  7. const fooBar: FooBarT = { foo: "123", bar: 12 }; // Works!
对象的maps (objects as maps)

虽然有了maps 这个数据结构 但是把对象当作maps 使用 依然很常见

</>复制代码

  1. // @flow
  2. var o: { [string]: number } = {}; // 制定key值的类型, 可以设置这个类型下的任何key值
  3. o["foo"] = 0;
  4. o["bar"] = 1;
  5. var foo: number = o["foo"];

索引也是一个可选的名字

</>复制代码

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

索引可以和命名属性混合

</>复制代码

  1. // @flow
  2. var obj: {
  3. size: number,
  4. [id: number]: string // 此处混合了 索引和命名
  5. } = {
  6. size: 0
  7. };
  8. function add(id: number, name: string) {
  9. obj[id] = name;
  10. obj.size++;
  11. }
对象类型(Object Type)

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

数组类型

</>复制代码

  1. let arr: Array = [1, 2, 3];
  2. let arr1: Array = [true, false, true];
  3. let arr2: Array = ["A", "B", "C"];
  4. let arr3: Array = [1, true, "three"]
简写

</>复制代码

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

?number[] === ?Array

数组的访问时不安全的

</>复制代码

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

你可以通过下面这样的做法来避免, flow 并未修复这个问题, 所以需要开发者自己注意

</>复制代码

  1. let array: Array = [0, 1, 2];
  2. let value: number | void = array[1];
  3. if (value !== undefined) {
  4. // number
  5. }
$ReadOnlyArray

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

</>复制代码

  1. // @flow
  2. const readonlyArray: $ReadOnlyArray = [1, 2, 3]

但是引用类型还是可以写的

</>复制代码

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

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

</>复制代码

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

可以把取出的值 赋值给具有一样类型的变量, 如果index 超出了索引范围,那么就会返回undefined 在 flow 中也就是 void

</>复制代码

  1. // @flow
  2. let tuple: [number, boolean, string] = [1, true, "three"];
  3. let num : number = tuple[0]; // Works!
  4. let bool : boolean = tuple[1]; // Works!
  5. let str : string = tuple[2]; // Works!

如果flow 不知道你要访问的时是那么类型, 那么他忽返回所有可能的类型,

</>复制代码

  1. // @flow
  2. let tuple: [number, boolean, string] = [1, true, "three"];
  3. function getItem(n: number) {
  4. let val: number | boolean | string = tuple[n];
  5. // ...
  6. }
tuple类型的长度一定要严格等于你声明时候的长度 tuple 不能匹配 数组类型, 这也是他们的差别 tuple 只能用 array 的 join() 方法 其他的都不可以用,否则报错 class type

javascript 的class 再flow 可以是值 也可以是类型

</>复制代码

  1. class MyClass {
  2. // ...
  3. }
  4. let myInstance: MyClass = new MyClass();

class 里的字段一定要声明类型了才可以用

</>复制代码

  1. // @flow
  2. class MyClass {
  3. prop: number;// 如果没有这行, 下的赋值会报错,因为prop 没确定类型
  4. method() {
  5. this.prop = 42;
  6. }
  7. }

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

</>复制代码

  1. // @flow
  2. function func_we_use_everywhere (x: number): number {
  3. return x + 1;
  4. }
  5. class MyClass {
  6. static constant: number; // 内部声明
  7. static helper: (number) => number;
  8. method: number => number;
  9. }
  10. MyClass.helper = func_we_use_everywhere
  11. MyClass.constant = 42 // 外部使用
  12. MyClass.prototype.method = func_we_use_everywhere

声明并且赋值的语法

</>复制代码

  1. class MyClass {
  2. prop: number = 42;
  3. }
类的泛型

</>复制代码

  1. class MyClass {
  2. property: A;
  3. method(val: B): C {
  4. // ...
  5. }
  6. }

如果你要把class作为一个类型,你声明了几个泛型, 你就要传几个参数

</>复制代码

  1. // @flow
  2. class MyClass {
  3. constructor(arg1: A, arg2: B, arg3: C) {
  4. // ...
  5. }
  6. }
  7. var val: MyClass = new MyClass(1, true, "three");
别名类型(type aliases)

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

</>复制代码

  1. // @flow
  2. type MyObject = {
  3. foo: number,
  4. bar: boolean,
  5. baz: string,
  6. };

这个是类型别名 可以在不同的地方复用

</>复制代码

  1. // @flow
  2. type MyObject = {
  3. // ...
  4. };
  5. var val: MyObject = { /* ... */ };
  6. function method(val: MyObject) { /* ... */ }
  7. class Foo { constructor(val: MyObject) { /* ... */ } }
别名泛型

</>复制代码

  1. type MyObject = {
  2. property: A,
  3. method(val: B): C,
  4. };

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

</>复制代码

  1. // @flow
  2. type MyObject = {
  3. foo: A,
  4. bar: B,
  5. baz: C,
  6. };
  7. var val: MyObject = {
  8. foo: 1,
  9. bar: true,
  10. baz: "three",
  11. };
不透明的类型别名(opaque type aliases)

通过类型系统的加强抽象

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

</>复制代码

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

不透明类型别名可以复用

</>复制代码

  1. // @flow
  2. // 在这个例子,我理解的是 外部只能访问到这个文件的ID 类型, 并不能访问到这个文件里面的string 基础类型. 这就是不透明的类型别名
  3. opaque type ID = string;
  4. function identity(x: ID): ID {
  5. return x;
  6. }
  7. export type {ID};

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

</>复制代码

  1. opaque type Alias: SuperType = Type;

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

</>复制代码

  1. opaque type StringAlias = string;
  2. opaque type ObjectAlias = {
  3. property: string,
  4. method(): number,
  5. };
  6. opaque type UnionAlias = 1 | 2 | 3;
  7. opaque type AliasAlias: ObjectAlias = ObjectAlias;
  8. opaque type VeryOpaque: AliasAlias = ObjectAlias;
不透明别名类型 的类型检查 在文件内部

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

</>复制代码

  1. //@flow
  2. opaque type NumberAlias = number;
  3. (0: NumberAlias);
  4. function add(x: NumberAlias, y: NumberAlias): NumberAlias {
  5. return x + y;
  6. }
  7. function toNumberAlias(x: number): NumberAlias { return x; }
  8. function toNumber(x: NumberAlias): number { return x; }
在文件外部

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

exports.js

</>复制代码

  1. export opaque type NumberAlias = number;

imports.js

</>复制代码

  1. import type {NumberAlias} from "./exports";
  2. (0: NumberAlias) // Error: 0 is not a NumberAlias!
  3. function convert(x: NumberAlias): number {
  4. return x; // Error: x is not a number!
  5. }
子类型约束(subTyping Constraints)

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

exports.js

</>复制代码

  1. export opaque type ID: string = string;

imports.js

</>复制代码

  1. import type {ID} from "./exports";
  2. function formatID(x: ID): string {
  3. return "ID: " + x; // Ok! IDs are strings.
  4. }
  5. function toID(x: string): ID {
  6. return x; // Error: strings are not IDs.
  7. }

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

</>复制代码

  1. //@flow
  2. opaque type Bad: string = number; // Error: number is not a subtype of string
  3. opaque type Good: {x: string} = {x: string, y: number};
泛型

不透明类型别名 有他们自己的泛型, 但是他们跟正常的泛型是差不多的

</>复制代码

  1. // @flow
  2. opaque type MyObject: { foo: A, bar: B } = {
  3. foo: A,
  4. bar: B,
  5. baz: C,
  6. };
  7. var val: MyObject = {
  8. foo: 1,
  9. bar: true,
  10. baz: "three",
  11. };
接口类型 (interface Types)

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

</>复制代码

  1. // @flow
  2. interface Serializable {
  3. serialize(): string;
  4. }
  5. class Foo {
  6. serialize() { return "[Foo]"; }
  7. }
  8. class Bar {
  9. serialize() { return "[Bar]"; }
  10. }
  11. const foo: Serializable = new Foo(); // Works!
  12. const bar: Serializable = new Bar(); // Works!

如果你怕出错, 你可以手动的 使用 implements 告诉flow 哪些类实现了哪些接口,这可以预防你修改class 的时候出现错误

</>复制代码

  1. // @flow
  2. interface Serializable {
  3. serialize(): string;
  4. }
  5. class Foo implements Serializable {
  6. serialize() { return "[Foo]"; } // Works!
  7. }
  8. class Bar implements Serializable {
  9. // $ExpectError
  10. serialize() { return 42; } // Error! // 不能返回一个number
  11. }

不要忘记了接口可以同时实现多个

接口的属性也是可以可选的

</>复制代码

  1. interface MyInterface {
  2. property?: string;
  3. }

接口跟maps 联合

</>复制代码

  1. interface MyInterface {
  2. [key: string]: number;
  3. }
接口泛型

</>复制代码

  1. interface MyInterface {
  2. property: A;
  3. method(val: B): C;
  4. }

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

</>复制代码

  1. // @flow
  2. interface MyInterface {
  3. foo: A;
  4. bar: B;
  5. baz: C;
  6. }
  7. var val: MyInterface = {
  8. foo: 1,
  9. bar: true,
  10. baz: "three",
  11. };
接口属性的 只读,与只写

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

</>复制代码

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

混合只读

</>复制代码

  1. interface MyInterface {
  2. +readOnly: number | string;
  3. }

允许指定多个类型

</>复制代码

  1. // @flow
  2. // $ExpectError
  3. interface Invariant { property: number | string }
  4. interface Covariant { +readOnly: number | string }
  5. var value1: Invariant = { property: 42 }; // Error!
  6. var value2: Covariant = { readOnly: 42 }; // Works!

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

</>复制代码

  1. // @flow
  2. interface Invariant { property: number | string }
  3. interface Covariant { +readOnly: number | string }
  4. function method1(value: Invariant) {
  5. value.property; // Works!
  6. value.property = 3.14; // Works!
  7. }
  8. function method2(value: Covariant) {
  9. value.readOnly; // Works!
  10. // $ExpectError
  11. value.readOnly = 3.14; // Error!
  12. }

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

</>复制代码

  1. // @flow
  2. interface Invariant { property: number }
  3. interface Contravariant { -writeOnly: number }
  4. var numberOrString = Math.random() > 0.5 ? 42 : "forty-two";
  5. // $ExpectError
  6. var value1: Invariant = { property: numberOrString }; // Error!
  7. var value2: Contravariant = { writeOnly: numberOrString }; // Works! 可以看到 上面声明了 number 可是这个numberOrString 有两种返回值, 他只能匹配一种 他野是可以传递的

通常比正常的属性更有用

</>复制代码

  1. interface Invariant { property: number }
  2. interface Contravariant { -writeOnly: number }
  3. function method1(value: Invariant) {
  4. value.property; // Works!
  5. value.property = 3.14; // Works!
  6. }
  7. function method2(value: Contravariant) {
  8. // $ExpectError
  9. value.writeOnly; // Error!
  10. value.writeOnly = 3.14; // Works!
  11. }
联盟类型 (union types)

类型的值可能是很多类型之一

使用 | 分开

</>复制代码

  1. Type1 | Type2 | ... | TypeN

可以竖直写

</>复制代码

  1. type Foo =
  2. | Type1
  3. | Type2
  4. | ...
  5. | TypeN

联盟类型可以组合

</>复制代码

  1. type Numbers = 1 | 2;
  2. type Colors = "red" | "blue"
  3. type Fish = Numbers | Colors;
联盟类型请求一个,但是所有的都要处理

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

</>复制代码

  1. // @flow
  2. // $ExpectError
  3. function toStringPrimitives(value: number | boolean | string): string { // Error!
  4. if (typeof value === "number") {
  5. return String(value);
  6. } else if (typeof value === "boolean") {
  7. return String(value);
  8. }
  9. // 注意这个函数会报错是因为 你用了if 条件语句 并没有在所有的情况中返回值, 如果返回了undefined 那么就不符合 string 类型,所以就报错了
  10. }
联盟改进

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

</>复制代码

  1. // @flow
  2. function toStringPrimitives(value: number | boolean | string) {
  3. if (typeof value === "number") {
  4. return value.toLocaleString([], { maximumSignificantDigits: 3 }); // Works!
  5. }
  6. // ...
  7. }
脱节联盟 (disjoint Unions)

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

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

</>复制代码

  1. { success: true, value: false };

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

</>复制代码

  1. { success: false, error: "Bad request" };

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

</>复制代码

  1. // @flow
  2. type Response = {
  3. success: boolean,
  4. value?: boolean,
  5. error?: string
  6. };
  7. function handleResponse(response: Response) {
  8. if (response.success) {
  9. // $ExpectError
  10. var value: boolean = response.value; // Error!
  11. } else {
  12. // $ExpectError
  13. var error: string = response.error; // Error!
  14. }
  15. }

取而代之,如果我们创建一个两个对象类型的联盟类型,Flow 会知道基于success 属性 我们会使用哪个对象

</>复制代码

  1. // @flow
  2. type Success = { success: true, value: boolean };
  3. type Failed = { success: false, error: string };
  4. type Response = Success | Failed; (这就是脱节联盟)
  5. function handleResponse(response: Response) {
  6. if (response.success) {
  7. var value: boolean = response.value; // Works!
  8. } else {
  9. var error: string = response.error; // Works!
  10. }
  11. }
脱节联盟与精确类型仪器使用

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

</>复制代码

  1. // @flow
  2. type Success = { success: true, value: boolean };
  3. type Failed = { error: true, message: string };
  4. function handleResponse(response: Success | Failed) {
  5. if (response.success) {
  6. // $ExpectError
  7. var value: boolean = response.value; // Error!
  8. }
  9. }
  10. // 不懂的跟上面的对比一下, 两个对象必须要有一个属性是相同的

然而 你可以用精确对象类型

</>复制代码

  1. // @flow
  2. type Success = {| success: true, value: boolean |};
  3. type Failed = {| error: true, message: string |};
  4. // 精确的也就是说 不可以扩展对象, 他该是哪个就是哪个 不存在混乱
  5. type Response = Success | Failed;
  6. function handleResponse(response: Response) {
  7. if (response.success) {
  8. var value: boolean = response.value;
  9. } else {
  10. var message: string = response.message;
  11. }
  12. }
交叉类型(intersection types)

所有不同类型的类型值

</>复制代码

  1. // @flow
  2. type A = { a: number };
  3. type B = { b: boolean };
  4. type C = { c: string };
  5. function method(value: A & B & C) {
  6. // ...
  7. }
  8. // $ExpectError
  9. method({ a: 1 }); // Error!
  10. // $ExpectError
  11. method({ a: 1, b: true }); // Error!
  12. method({ a: 1, b: true, c: "three" }); // Works!

可以把上面的连门类型理解为或 把交叉类型理解为& 语法都是一样的

</>复制代码

  1. type Foo =
  2. & Type1
  3. & Type2
  4. & ...
  5. & TypeN
  6. type Foo = Type1 & Type2;
  7. type Bar = Type3 & Type4;
  8. type Baz = Foo & Bar;

我们在函数中和联盟函数相反, 我们不如传入所有的类型,但是在函数里面我们只需要做处理一种情况就OK

</>复制代码

  1. // @flow
  2. type A = { a: number };
  3. type B = { b: boolean };
  4. type C = { c: string };
  5. function method(value: A & B & C) {
  6. var a: A = value;
  7. var b: B = value;
  8. var c: C = value;
  9. }
不可能的交叉类型

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

</>复制代码

  1. // @flow
  2. type NumberAndString = number & string;
  3. function method(value: NumberAndString) {
  4. // ...
  5. }
  6. // $ExpectError
  7. method(3.14); // Error!
  8. // $ExpectError
  9. method("hi"); // Error!
交叉对象类型

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

</>复制代码

  1. // @flow
  2. type One = { foo: number };
  3. type Two = { bar: boolean };
  4. type Both = One & Two;
  5. var value: Both = {
  6. foo: 1,
  7. bar: true
  8. };

如果声明的属性类型相同, 就相当于你声明了一个 交叉类型的属性

typeof Types (这个不好翻译 因为 typeof 是js中的一个关键字,在此我就不翻译这个了)

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

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

所以在flow中, 他把这个关键字重载了

</>复制代码

  1. // @flow
  2. let num1 = 42;
  3. let num2: typeof num1 = 3.14; // Works!
  4. // $ExpectError
  5. let num3: typeof num1 = "world"; // Error!
  6. let bool1 = true;
  7. let bool2: typeof bool1 = false; // Works!
  8. // $ExpectError
  9. let bool3: typeof bool1 = 42; // Error!
  10. let str1 = "hello";
  11. let str2: typeof str1 = "world"; // Works!
  12. // $ExpectError
  13. let str3: typeof str1 = false; // Error!

你可以typeof 任何值

</>复制代码

  1. // @flow
  2. let obj1 = { foo: 1, bar: true, baz: "three" };
  3. let obj2: typeof obj1 = { foo: 42, bar: false, baz: "hello" };
  4. let arr1 = [1, 2, 3];
  5. let arr2: typeof arr1 = [3, 2, 1];
引用类型的 typeof 继承行为

你可以用typeof 的返回值作为一个类型

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

</>复制代码

  1. // @flow
  2. let num1: 42 = 42;
  3. // $ExpectError
  4. let num2: typeof num1 = 3.14; // Error!
  5. // 看这里 num1 的type 指定了是 42 那么 typeof num1 不会返回number 会返回 42 所以3.14 不符合
  6. let bool1: true = true;
  7. // $ExpectError
  8. let bool2: typeof bool1 = false; // Error!
  9. let str1: "hello" = "hello";
  10. // $ExpectError
  11. let str2: typeof str1 = "world"; // Error!
其他类型的 typeof 继承行为

</>复制代码

  1. // @flow
  2. class MyClass {
  3. method(val: number) { /* ... */ }
  4. }
  5. class YourClass {
  6. method(val: number) { /* ... */ }
  7. }
  8. // $ExpectError
  9. let test1: typeof MyClass = YourClass; // Error!
  10. let test2: typeof MyClass = MyClass; // Works!
  11. // 看这里 es6 的类并不是一种类型, 只是一种语法糖而已,内部机制还是原型链, 所以 typeof MyClass 不会等于YourClass
镶嵌表达式类型(type casting expression)

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

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

语法

</>复制代码

  1. (value: Type)

这个表达式可以出现在表达式能出现的任何地方

</>复制代码

  1. let val = (value: Type);
  2. let obj = { prop: (value: Type) };
  3. let arr = ([(value: Type), (value: Type)]: Array);

也可以这样写

</>复制代码

  1. (2 + 2: number);
类型断言

</>复制代码

  1. // @flow
  2. let value = 42;
  3. // 这个的作用就是把变量 嵌入到一个类型中去
  4. (value: 42); // Works!
  5. (value: number); // Works!
  6. (value: string); // Error!
类型嵌入

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

</>复制代码

  1. // @flow
  2. let value = 42;
  3. (value: 42); // Works!
  4. (value: number); // Works!
  5. let newValue = (value: number);
  6. // $ExpectError
  7. (newValue: 42); // Error!
  8. (newValue: number); // Works!
通过 any 去转换类型

</>复制代码

  1. let value = 42;
  2. (value: number); // Works!
  3. // $ExpectError
  4. (value: string); // Error!
  5. // 这里先把value 变成any 再变成string
  6. let newValue = ((value: any): string);
  7. // $ExpectError
  8. (newValue: number); // Error!
  9. // 生效了
  10. (newValue: string); // Works!

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

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

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

想这样:

</>复制代码

  1. function clone(obj: { [key: string]: mixed }) {
  2. const cloneobj = {};
  3. Object.keys(obj).forEach(key => {
  4. cloneobj[key] = obj[key];
  5. });
  6. return ((cloneobj: any): typeof obj);
  7. }
  8. const obj = clone({foo: 1})
  9. (obj.foo: 1) // 出错!

</>复制代码

  1. function clone(obj) {
  2. (obj: { [key: string]: mixed });
  3. const cloneobj = {};
  4. Object.keys(obj).forEach(key => {
  5. cloneobj[key] = obj[key];
  6. });
  7. return ((cloneobj: any): typeof obj);
  8. }
  9. const obj = clone({foo: 1})
  10. (obj.foo: 1) // ok!
工具类型

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

详情看这里

模块类型

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

注释类型

感觉没多大用处, 可以做一些标记,这个可以在不通过flow 编译的情况下直接使用在js文件上

</>复制代码

  1. // @flow
  2. /*::
  3. type MyAlias = {
  4. foo: number,
  5. bar: boolean,
  6. baz: string,
  7. };
  8. */
  9. function method(value /*: MyAlias */) /*: boolean */ {
  10. return value.bar;
  11. }
  12. method({ foo: 1, bar: true, baz: ["oops"] });

看完能看懂所有flow 代码了吧...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/97778.html

相关文章

  • Flow - JS静态类型检查工具

    摘要:介绍是个的静态类型检查工具,由出品的开源码项目,问世只有一年多,是个相当年轻的项目。现在,提供了另一个新的选项,它是一种强静态类型的辅助检查工具。 showImg(https://segmentfault.com/img/bVH6mL?w=1200&h=675); 本章的目标是提供一些Flow工具的介绍与使用建议。Flow本质上也只是个检查工具,它并不会自动修正代码中的错误,也不会强制...

    seanHai 评论0 收藏0
  • Flow, 一个新的Javascript静态类型检查器

    摘要:原文链接翻译于今天我们兴奋的发布了的尝鲜版,一个新的静态类型检查器。为添加了静态类型检查,以提高开发效率和代码质量。这最终形成一个高度并行增量式的检查架构,类似。知道缩小类型范围时做动态检查的影响。 原文链接:https://code.facebook.com/posts/1505962329687926/flow-a-new-static-type-checker-for-java...

    liangzai_cool 评论0 收藏0
  • Vue源码之目录结构

    摘要:运行时用来创建实例渲染并处理虚拟等的代码。基本上就是除去编译器的其它一切。版本可以通过标签直接用在浏览器中。为这些打包工具提供的默认文件是只有运行时的构建。为浏览器提供的用于在现代浏览器中通过直接导入。 Vue版本:2.6.9 源码结构图 ├─ .circleci // 包含CircleCI持续集成/持续部署工具的配置文件 ├─ .github ...

    freewolf 评论0 收藏0
  • Vue源码之目录结构

    摘要:运行时用来创建实例渲染并处理虚拟等的代码。基本上就是除去编译器的其它一切。版本可以通过标签直接用在浏览器中。为这些打包工具提供的默认文件是只有运行时的构建。为浏览器提供的用于在现代浏览器中通过直接导入。 Vue版本:2.6.9 源码结构图 ├─ .circleci // 包含CircleCI持续集成/持续部署工具的配置文件 ├─ .github ...

    icattlecoder 评论0 收藏0

发表评论

0条评论

quietin

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<