Flow和TypeScript之间的区别和优劣
采用Flow&TypeScript, 两者上手流程之间的比较。
让我们想象一下我们想要采用一种类型检查器的场景…
最近我们已经注意到了我们的应用程序中的很多NaN的出现。我们搜索源代码并找到以下代码:
// math.js
function square(n) {
return n * n;
}
square("oops");
我们对自己叹了口气,也许决定添加一个类型检查器。我们退后一步,查看我们的选项: Flow 或 TypeScript。
这两个工具都有相当简单的方法给逐个文件应用:
- Flow:
// @flow
向文件顶部添加注释 - TypeScript:将扩展名更改为
.js
扩展.ts
名
但是让我们比较一下这里面发生了什么。
采用TypeScript
要采用TypeScript,我们首先重命名math.js
为 math.ts
:
// math.ts
function square(n) {
return n * n;
}
square("oops");
现在我们将运行typescript:
(no errors)
没有错误,是因为TypeScript要求我们给函数键入注释,然后才会根据注释检查类型。如下所示:
function square(n: number): number {
return n * n;
}
square("oops");
如果没有这些类型,TypeScript将根据您的配置执行下面两件事情之一:
- 隐含地将每个未知类型转换为
any
。这种任何类型将让您退出所有类型检查。
- 隐含地将每个未知类型转换为
- 或者如果您使用了
--noImplicitAny
选项,它会为任何未知类型抛出错误,指明需要添加类型注释。
- 或者如果您使用了
这意味着TypeScript 覆盖的代码量与您所写的类型相关。写入类型时,类型coverage将线性上升。
类型覆盖(type coverage)
在我们进一步讲解之前,我应该解释一下什么是类型覆盖。
未有类型覆盖的代码用红色显示
如果你看到你的代码中的值和表达式,并问类型检查器“你知道这是什么类型吗”。
如果类型检查器知道它类型,则覆盖该值或表达式。如果类型检查器不知道类型,那么它没有被覆盖。
您希望您的程序尽可能多地提供类型覆盖,因为这样可以在更多地方抛出错误时提前告诉您。
没有类型覆盖,类型检查器什么都不是。
采用Flow
// @flow
function square(n) {
return n * n;
}
square("oops");
然后我们将运行Flow并查看结果:
function square(n) {
return n * n;
^ ^
Error (x2)
}
square("oops");
Error (x2)
string. The operand of an arithmetic operation must be a number.
紧接着就抛出了类型错误,告诉我们代码出了问题。
Flow 只需要我们键入文件和外部模块的导出。可以推测出其他一切。
这使得类型覆盖率快得多。只需几种类型,您可以快速获取具有非常高类型覆盖率的文件。
根据我的经验,我可以在短短几分钟内将文件覆盖约70-90%。
这是一个非常科学的差异图:
这不是我一家之言,你可以自己尝试一下,看看几种类型的区别。
要查看Flow中文件的类型覆盖,可以运行:
flow coverage path/to/file.js --color
您还可以使用 流量报告 来帮助您。
注意:我没有注意到TypeScript 有任何的类型报告工具(如果您知道一个,请给我发送一个链接)。但是,您可以测试代码是否覆盖,以确定当您出现错误时是否会报告错误。
这个的工作原理是什么
这两种工具具有不同行为的原因归结于其架构之间的区别。
TypeScript体系结构:AST导向
TypeScript将遍历您的程序并构建已知类型的表。当它发现值和表达式时,它会立即为其分配类型。当TypeScript发现一个未知的类型时,它必须立即作出决定,这意味着将其分配给any
或抛出错误。
Flow 架构:图形导向
Flow 将建立一个你所有的值和表达式及其彼此之间的关系的图表。然后,它将开始为每个值和表达式分配类型。如果它找到一个未知的类型,它将使它成为一个“开放”类型,稍后再回来判断。
一旦Flow具有您的程序的完整蓝图,它将开始连接所有点,从一个值连接到另一个值地跟踪类型。打开类型接受流入它们的所有值的类型 - 生成的类型称为 “推断类型”。
你可以看这个是怎么回事。来看看我们在之前的类型错误:
function square(n) {
return n * n;
^ ^
Error (x2)
}
square("oops");
Error (x2)
string. The operand of an arithmetic operation must be a number.
注意错误是指向n * n
而不是 square("oops")
。因为我们没有写入一个类型为n的 “oops”字符串流入它,并且Flow开始检查n,就好像它是一个字符串。
添加类型注释我们可以看到错误点移动了:
function square(n: number) {
return n * n;
}
square("oops");
^ Error
Error: string.
This type is incompatible with the expected param type of number.
这提出了一个重要的一点:Flow可以在任何地方自动推断类型并不意味着你不应该添加类型注释你的代码。
结论
TypeScript和Flow都有非常好的上手过程。一个个文件地尝试是一个很好的经历。
但是,如果使用Flow,你就会有更高以及更快的覆盖类型,你就可以安心睡觉。
使用Flow,您可以添加类型以使错误更友好,而不仅只是发现它们。