类型推断

那在Flow当中除了使用类型注解的方式去标记我们代码当中每一个成员的类型,Flow它还可以很聪明的,主动帮我们推断我们代码当中的每个类型。

例如我们这里去定一个square函数,那这个函数接收一个参数在这个函数内部返回这个参数的平方。

那很明显我们这里的参数只能够接收数字类型的参数,也就是说正常我们应该给这个square函数传入一个:number的参数, 确保我们只会去接收一个数字参数。

那这里即便是我们没有添加这个类型注解,我们直接去调用这个函数,我们在调用的时候传入的是一个非数字参数。

function square(n) {
    return n * n;
}
square('100');

Flow这个时候他仍然可以帮我们发现,我们在这个类型使用上的错误,那他会根据我们在调用时传入的是字符串推断出我们这里的参数接收到的是一个字符串类型, 而字符串类型是不能够进行乘法运算的,所以这里就会报错。

那这种根据我们代码当中的使用情况去推断出来我们变量的类型的这样的特征就叫做类型推断,不过我们绝大多数情况下还是建议大家为代码当中的每个成员添加类型注解。因为这样的话可以让我们代码有更好的可读性。

类型注解

在绝大多数情况下,Flow都可以像刚刚所说的一样他可以帮我们推断出来变量或者是参数的具体类型。

所以说从这个角度上来讲,我们实际上没有必要给所有的成员都去添加类型注解,但是我们去添加类型注解他可以更明确的去限制类型,而且对我们后期去理解这里的代码呢也是更有帮助的。

所以说我们建议大家还是尽可能去使用类型注解,那类型注解呢,它不仅仅可以用在我们函数的参数上,这里还可以用来去标记变量的类型以及我们函数返回值的类型。

那用在变量上就是在我们变量名后面跟上:类型的名称,那这样的话我们这个变量就只能够去存放这种类型的数据,如果说我们赋值的是其他类型的数据,就会报出语法错误。

let num: number = 123;

去标记函数返回值类型呢就是在函数的参数括号后面去跟上:类型名称,那此时这个函数就只允许去返回这个类型的返回值,如果返回的是一个其他类型的值,这里也会报出语法错误。

function foo(): number {
    return 100;
}

这里还有一个需要注意的地方就是, 如果说函数没有返回值的话,在js当中没有返回值默认返回的就是undefined,所以说他也会报语法错误。对于我没有返回值的函数我们应该将它的返回值类型标记为void。

function foo(): void {

}

原始类型

在用法上Flow几乎没有任何的难度,那无外乎就是使用Flow命令去根据我们代码当中添加的类型注解去检测我们代码当中的那些类型使用上的异常。

那这里值得我们再去了解的无外乎就是Flow当中具体支持哪些类型以及我们在类型注解上有没有一些更高级的用法,这里我们具体来看。

在Flow当中能够使用的类型有很多,首先最简单的自然就是js当中所有的原始数据类型,我们可以快速来尝试一下。

在js当中的原始数据类型现在应该是有6种,分别是string,number,boolean,null,undefined 以及最新ES6提供的 symbol。当然在以后还会有一个bigInt,现在刚刚被标准化。

这里我们可以快速尝试一下,首先是string类型,那这种类型的变量呢,他要求只能够去存放字符串类型,这没什么可说的。

const a: string = 'foobar';

然后就是number类型,number类型的变量他可以用于存放数字,它还可以用来存放NaN, 这个表示的是一个非数字,在js当中他也属于number类型, 所以number类型变量也可以用于存放NaN。

除此之外还有一个Infinity,也就是无穷大,这也是js当中number的一个特殊值,表示无穷大的一个值。

const b: number = NaN;

再有就是boolean类型,那这种类型值能够存放两个值,一个是true,一个是false,这也没什么特殊情况。

const c: number = NaN;

那再然后就是null,那这个类型只有一种情况,就是他本身。

const d: null = null;

与null相类似的还有一个就是undefined, 这里需要注意的是Flow当中这个undefined是用volid表示的。也就是说我们要想给一个变量当中去存放undefined,我们需要把它的类型标记为void,这一点跟函数返回值返回undefined是一个道理

const e: void = undefined;

最后还有我们在ES6中提供的symbol,那他就只能存放symbol类型的值了。

const f: symbol = Symbol();

其实我们也不需要刻意记忆这些Flow当中的原始类型,你多去用一用自然也就熟悉了。

数组类型

除了对普通的数值做类型限制的原始类型,在Flow当中还支持对有结构的数据做类型限制,那这里有结构的数据无外乎就是对象或者是数组,那这里我们先来看一下数组类型。

那Flow当中支持两种数组类型的表示方法,第一种就是使用Array类型,不过这个类型他需要一个泛型参数,用来去表述这个数组当中的每一个这个数组当中的每一个元素的类型。

我们可以在Array的后面用一对尖括号去指定,例如我们这里把元素的类型指定为number。

const arr: Array<number> = [1, 2, 3, 4];

关于泛型这个概念呢, 我们在后面的ts当中会去详细介绍,这里我们就只需要知道,这个Array<number>表示的是一个全部由数字组成的数组,那这个变量的值也就必须要是一个全是数字的数组。

如果我们在数组当中出现了其他的类型,就会报出语法错误。那这是第一种方式。

第二种方式就在元素类型后面跟上一个数组类型的方括号,这种方式同样可以表示一个全部由于数字组成的数组。

const arr: number[] = [1, 2, 3, 4];

以上就是表示数组类型的两种方法,那除此之外如果我们需要去表示一个固定长度的数组,我们可以使用一种类似于数组字面量的语法去表示。

例如我们这里定义一个叫做foo的变量,那他的类型是一个数组,然后在数组当中我们第一个类型放的是string类型,第二个元素是number类型,那这个时候我们在这个变量当中就只能够去存放一个包含两位元素的数组。

const foo: [string, number] = ['foo', 100];

而且第一个元素必须是字符串,第二个是数字。

那对于这种固定长度的数组,我们有一个更专业的名称,叫做元组,一般这种元组我们在一个函数当中同时要去返回多个返回值的时候我们就可以使用这种元组的数据类型。

对象类型

对象是js当中最常见的数据结构,在Flow当中去描述对象类型的方式跟对象字面量语法非常类似,那我们具体来看。

这里我们先去定义一个obj的变量,如果我们需要限制这个变量只能够是对象类型的话,那我们就可以在他的类型注解上使用一对花括号。

然后在这对花括号里面我们就可以去添加具体的成员名称和对应的类型限制,例如我们这里先去添加一个叫做foo的成员, 然后他的类型是string,然后我们可以再去添加一个bar 的成员,那这个类型我们给他设置为number。

const obj: {foo: string, bar: number } = {foo: 'str', bar: 100 };

那这样的话就表示我们在当前这个变量当中所存放的对象他必须要具有foo和bar这两个成员,而且他们的类型分别是string和number。

那如果我们需要其中某一个成员是可选的,那我们就可以在这个成员的名称后面添加一个?, 那这样的话这个成员就是可有可无的。

const obj: {foo?: string, bar: number } = { bar: 100 };

那除此之外对于对象很多时候我们会把他当做键值对集合去使用,也就是我们在初始化对象时并不去添加任何的属性,然后在后续代码执行的过程中动态去添加一些键值。

那这种情况默认就是被允许的,不过在默认这种情况下我们还是可以使用任意类型的键和任意类型的值。

那如果我们需要明确去限制键和值的类型,那我们这里可以使用一种类似索引器的语法去设置,也就是把对象类型当中的这个属性名位置修改为[], 然后在里面去指定他的键是什么类型的,那我们这里设置为string然后冒号后面仍然使我们值对应的类型。

const obj: { [string]: string} = {};

那这种类型键值的意思呢就是表示我们当前这个对象允许添加任意个数的键,不过呢他的键的类型和值的类型都必须是字符串。

那这就是对象的一些类型的限制。

函数类型

对于函数的类型限制,一般指的就是对函数的参数类型和返回值类型进行对应的约束。

那对于函数的参数类型限制我们可以在参数的名字后面跟上一个类型注解。然后对于返回值的类型是在函数的这个括号后面去添加对应的类型注解,那这点之前我们已经介绍过了。

除此之外呢,因为函数在js当中也是一种特殊的数据类型,很多时候我们也会把函数放到变量当中,例如我们去传递回调函数作为参数的时候,就会把一个函数放到一个回调参数的变量当中。

那这种情况下我们该如何去限制我们存放函数的这种变量的类型呢。我们具体来看。

那这里呢我们可以先去定义一个函数,那这个函数他接收一个回调函数参数,然后在这个函数的内部我们去调用这个callback参数。

function foo (callback) {
    callback('string', 100);
}

如果我们想去限制这个回调函数的参数和他的返回值,那这里我们可以使用一种类似于箭头函数的一种函数签名的类型去限制,例如我们这要去要求这个回调函数他必须要有两个参数,第一个是string类型的第二个是number类型的。

然后我们在箭头的右边,也就是返回值的位置我们去指定这个函数的返回值是void,也就是说他没有返回值。

function foo (callback: (string, number) => void) {
    callback('string', 100);
}

那此时我们再去调用这个foo函数的时候我们传入的这个回调函数那就必须要遵循刚刚这样一个限制,也就是他可以接收两个参数,分别是string和number,然后在这个函数内部是不可以有返回值的,或者说是返回undefined。

function foo (callback: (string, number) => void) {
    callback('string', 100);
}

foo(function(str, a) {
    // str => string;
    // a => number
})

这就是函数的类型限制。

特殊类型

除了常规的几种原始数据类型以外,在Flow当中还支持几种特殊的类型或者说是几种特殊的情况。这里我们来依次了解一下。

首先是一个叫做字面量的类型,与传统的类型不同的是,这种字面量类型他是用来去限制我们的变量必须是某一个值。

例如我们这声明一个叫做a的变量,然后他的类型我们用一foo的字符串去表示,那这是一个字面量,那此时我们这个a变量当中就只能够去存放这个字符串的值,也就是一个foo字符串。如果是其他任何的字符串都会报错。当然其他类型更不用说了。

const a: 'foo' = 'foo';

那这种字面量类型他一般不会单独去使用,而是配合一个叫联合类型的用法去组合几个特定的值,例如我们这定义一个叫做type的变量,然后他的类型是success|warning|danger。

const type: 'success' | 'warning' | 'danger' = 'success';

那此时我们这个type变量就只能存放这三种值的其中之一, 那这就是字面量类型。

那再者就是刚刚我们所看到的这种联合类型的用法,或者也可以叫做或类型,那他不仅可以用在我们这种字面量上面,还可以用在普通的类型上面。

例如我们这里定义一个b变量,那这个变量的类型是string | number, 那也就是说我们这个变量的值可以是字符串或者是数字都是没有问题的。

const b: string | number = 'string'; // 100

我们这还可以使用type关键词去做一个单独的声明去声明一个类型,用来去表示多个类型联合过后的结果。

例如我们这定义一个叫做StringOrNumber的一个类型,那他这个类型的具体情况呢就是string或者number,那这样的话我们这个StringOrNumber他就是一个相当于一个类型的别名。

const StringOrNumber = string | number;
const b: StringOrNumbe = 'string'; // 100

然后我们可以在多个地方重复去使用这个类型了。

那这就是通过type关键词去给类型做一个别名或者可以说是用type去单独声明一个类型。

那除此之外在Flow当中还支持一种叫做maybe的类型,就是有可能,例如我们这定义一个叫做gender的变量,那他的类型是number,那此时这个变量他是不能为空的,也就是不能是null或者undefined。

那如果我们需要这个变量可以为空的话,我们就可以在这个number前面去添加一个问号,去表示我们这个变量除了可以接收number以外它还可以接收null或者undefined。

const gender: ?number = null;

那也就是这种maybe类型他在具体的类型基础之上扩展了null和undefined这两个值,那这种用法呢实际上就相当于是number | null | void, 那这就是我们所谓的maybe类型。

mixed & any

那除此之外呢还有两个特殊的类型呢就是mixed和any,那我们分别来看一下。

那首先是mixed类型,那这个类型呢他可以用来去接收任意类型的值,我们这里可以尝试定义一个叫做passMixed的函数,然后我们在这个函数中去接收一个value参数,这个参数类型我们标记为mixed。

function passMixed(value: mixed) {

}

那此时我们去调用这个函数时我们就可以传入任何类型的数据了。那这种叫做mixed的类型它实际上就是所有类型的一个联合类型。

那除了mixed类型可以接收所有类型以外,还有一个叫做any的类型,他也有类似这样的一个效果。

那这里我们再来定义一个叫做passAny的函数,那这个函数同样接收一个value参数,然后我们将这个value标记为any类型。

function passAny(value: any) {

}

那此时我们依然可以对这个函数传入任意类型的参数,结果也都不会有任何的问题。

那此时你可能就会在想,既然都是接收任意类型的数据,那他们两者之间的差异到底在什么地方呢。

那可以用一句话来说就是any他是弱类型,而我们的mixed他仍然还是强类型的,那为什么这么说呢,我们这试验一下就可以明白。

这里我们在passAny函数的内部我们可以把这个value当做任意类型去使用,例如我们当成字符串,那我们就可以调用他的substr方法,当然我们只是举其中一个例子,或者你也可以把他当做一个数字。结果都不会报错,当然了这里说的不会报错指的是语法上不会报错。并不是说运行的阶段不会报错。

function passAny(value: any) {
    value.substr(1)
}

那也就是说我们这个any他仍然是弱类型的。

而mixed他则完全不一样,那如果说我们也是相同的直接把他当做字符串或者是数字去使用,结果就会直接报处语法错误。

因为在这个地方mixed类型他是一个具体的类型,如果我们没有明确他内部是一个字符串的话,那我们是不可以把他当做字符串去使用的。

那这里我们想要明确这个mixed类型的value到底是不是个字符串,那我们可以通过typeof这种方式去明确,也就是我们以前传统的类型判断的方式。

我们可以在这个运行阶段去判断一下,我们这个value他到底是不是个字符串,如果是的话我们再按照字符串的方式去调用里面的substr方法或是其他别的方法,结果就不会报错了。

function passMixed(value: mixed) {
    if (typeof value === 'string') {
        value.substr(1)
    }
}

那这就很明显了,我们这里使用mixed类型,他仍然是安全的,因为对于我们这个地方如果说,在类型使用上存在隐患的话他仍然会报错,而我们加了类型判断过后,他实际上就解决了这种类型隐患。

那相比较来讲的话,any则是不安全的,所以说我们在实际使用的过程当中,尽量不要去使用any类型。

那any类型存在的意义呢,其实主要是为了兼容以前的一些老代码,因为在很多的陈旧代码当中我们可能会借助于js的弱类型或者动态类型去做一些特殊的情况。那些情况如果要被我们这个地方兼容我们就需要用到any这样一个类型。

类型小结

那关于Flow当中的类型我们这里只是了解了一部分常见的,其实他还有很多很多,这里呢我们不可能去一一介绍,一一介绍也没有什么太大的意义。

对于Flow的学习我们其实最主要的目的就是为了可以在以后我们去理解一些例如像vue或者是react的一些第三方项目的源码时,可能会遇到这个项目中使用了Flow的情况,所以说我们必须要能够看懂。

不过呢,在这些项目当中他可能会存在一部分我们没有了解过的类型,那这些类型呢到时候可以再去查一下相应的文档。

下面链接就是Flow的官网当中对所有类型的一个描述文档。

https://flow.org/en/docs/types

除此之外呢,我们这里推荐一个第三方的类型手册,这个类型手册整理的更为直观,更适合我们在当前这种了解过Flow过后,然后再去做对应的查询。

https://www.saltycrane.com/cheat-sheets/flow-type/latest/

如果说我们在访问这些链接的时候遇到打开的情况,就可以借助于科学上网的工具去访问。

运行环境

最后呢我们还需要在了解一下Flow当中对一些运行环境所提供的一些API的支持,那为什么这么说呢,因为我们的js他不是独立工作的,他必须要运行在某一个特定的运行环境当中。

例如浏览器环境或者是Node环境,那这个运行环境呢他一定会提供一些API给我们使用。

例如我们在浏览器环境中有DOM和BOM,在node环境中有各种各样的模块,那这也就是说我们的代码他必然会使用到这些环境中所提供的一些API或者是一些对象。

那对于这些API和对象,他同样会有一定的类型限制,例如我们调用一下DOM中获取元素的方法。这个方法就要求我们必须要传入一个字符串,如果我们传入数字的话就会报错。

document.getElementById('app');

那这就属于浏览器环境所内置的一些API所对应的一些类型限制,那这个函数的方法的返回的就是一个HTMLElement类型,而且如果我们没有找到对应的元素他还有可能返回null。

const element: HTMLElement | null = document.getElementById('app');

那这就属于运行环境所内置的一些类型限制。下面列出Flow针对不同环境提供的一些API。

https://github.com/facebook/flow/blob/master/lib/core.js

https://github.com/facebook/flow/blob/master/lib/dom.js

https://github.com/facebook/flow/blob/master/lib/bom.js

https://github.com/facebook/flow/blob/master/lib/cssom.js

https://github.com/facebook/flow/blob/master/lib/node.js


欢迎关注,更多系列文章