Flow vs. Typescript
Posted 大前端工程师
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flow vs. Typescript相关的知识,希望对你有一定的参考价值。
本文译自 Flow vs. Typescript。大家可以阅读原文访问我们的知乎专栏对应的文章。
我们有一个很大的基于 React 的项目,它已经持续了一年时间,从项目第一天起我们就在其中使用了 Flow。我一直想要在 javascript 语言中做到对类型的支持,但在当时 TypeScript 并不在考虑之列,因为那时的 TypeScript 还缺少一些我认为理所当然应该具备的特性(ES6/7 的语法特性,特别是像数组和对象的解构)。因此我们选择了 Flow,也是考虑到 React 和 Flow 都是 Facebook 的项目,估计它们在一起能工作得比较好吧。
一年过去了,现在我们却正在用 TypeScript 彻底取代 Flow。在过去的一年里,TypeScript 添补上了所有我需要的特性,而 Flow 则开始暴露出一些严重的问题。通过这篇文章,我想列出一些导致我们做出切换决定的原因,同时也为那些试图做同样事情的项目给出一些建议。
无用的错误信息
尽管 Facebook 已经在改善 Flow 的错误信息方面做出了一些尝试,但目前的状态仍然是在通常情况下,我们可以知道哪里出现了错误,却不知道什么导致了这个错误。我们一次又一次遇到的最可怕的事情莫过于 IncompatibleinstantiationforT
, T
是一个类型变量。
Flow 不会告诉你这个不兼容的实例化到底是什么,也不会告诉你这个实例在哪里,让你去猜,要么你就得通过到处添加更多的显式注解的方式来找出问题所在。我们从来没有能解决这个问题,通常的做法就是容忍这种错误。
再来看 TypeScript,使用 TypeScript 时我们也会出现同样的问题,但 TypeScript 会准确地告诉你在哪里流入了一个不兼容的类型,类型的哪一部分不兼容,并列出所有可能的替代选择。这样就足以快速找到这些问题的源头了。
使用 TypeScript 时也会遇到一些棘手的问题难以解决。为了避开这些问题,一个可行的办法是把一些表达式的类型转换成 any
,然后再转换成你知道它肯定是的那种类型。你需要显式地做这件事,但这样你可以确保无论出现什么问题都不会扩散到别的地方去。
社区以及与 React 的关系
我原本以为对一个 React 项目来说 Flow 会是一个理想的选择,毕竟它们都是在 Facebook 开发出来的。但经过这一年我并没有感觉到 React 和 Flow 的团队之间有太多的交流。
在 React 中有一些与正规 JavaScript 非常不同的编程模式。既然 Flow 和 React 出自同一家公司,我想当然地认为 Flow 对这些模式会支持地更好。然而最后我们发现,Flow 在这方面的表现与 TypeScript 并没有什么不同。
我认为这标志了 Facebook 并没有对 Flow 给予应有的投资,而另一方面微软却非常重视 TypeScript。TypeScript 的发展很快,就新特性与社区展开开放式的讨论,他们甚至为 TypeScript 开发了专门的编辑工具(这一点后面我会做更多讨论)。
我并没有一个准确的数字,但感觉使用 TypeScript 的开发者要比使用 Flow 的多。这意味着 TypeScript 会有大量更好的资料,当我遇到问题的时候,也会有更多的地方去寻求帮助和解决方案。公平地说,最近(2017年2月)关注 Flow 的人似乎多了一些,但仍然不及 TypeScript。
糟糕的工具
让 Flow 运行起来并非易事。Mac 用户非常幸运,通过 homebrew 可以安装预制的二进制包。但如果你需要自己编译它,你就先得建立一套 OCaml 开发环境。
Flow 会启动一个服务器进程来监视你的文件,然后可以向其查询文件中存在的错误以及类型信息。但这些查询完全可以通过 Flow 的可执行程序来给出,而不需要依赖一个一直保持运行的服务器。
Flow 通过第三方插件与各种编辑器和 IDE 进行整合。公平地讲,我只试用过 Vim,我的一个同事则成功使用过 Atom 中的插件。但用 Flow 进行开发不会让人感觉是在使用一种类型语言,会明显地感觉到 Flow 只是一种额外附加在无类型语言之上的类型系统。
Flow 还提供了一些辅助工具来帮助你分析代码。有一个 covergae
命令可以告诉你在某个文件中有多少 Flow 可以理解的表达式。它还提供了一个 --debug
选项,运行之后其输出就像这样:
$ flow coverage --debug src/index.js
5:6,5:16: (true)
5:19,5:27: (true)
5:19,5:49: (true)
5:43,5:48: (true)
7:0,7:6: (false)
7:0,7:26: (false)
7:7,7:13: (false)
这有什么用呢?你也可以辩解说 Flow 可以打印出你的文件,把有问题的表达式用红色标识出来。这就更好玩了,当我对那些 Flow 认为对或者错的地方使用 type-at-pos
命令时,它又经常说 (unknown)
。
再来看 TypeScript,它是一门具备自己规范的独立的编程语言,它只是碰巧可以被翻译成 JavaScript。语言规范的存在会让人觉得它比其它项目更为稳定。TypeScript 自己不需要与无类型代码做妥协(尽管它让你来做这些事),从而可以在一个更严格规范的环境中运作。
而在工具方面,TypeScript 的最大优势来自于 Visual Studio Code,这是一个基于 Electron 开发的编辑器,类似于 Github 的 Atom。用 VSCode 编辑 TypeScript 会让你体验到类似一个大型 IDE 的强大能力和方便性。跳转到定义、自动补全、JSDoc 提示、重构命令、类型错误的行内显示,这些都是我离不开的功能。在它们的帮助下,开发效率有了巨大提升!
缺乏信心
据说 Flow 有 strict
和 loose
两种不同的模式,但我发现即便使用 strict
模式,它也无法防止无类型代码通过检查,而且会让人觉得无类型代码具有一种传染性,只要与之发生关系的代码都会随之变成无类型代码。一些本来曾经是有类型的代码文件会悄悄地变成无类型的,于是Flow 就不再报告这些文件中的错误。除非你使用 type-at-pos
命令去手工对文件中的表达式进行采样试验,否则你永远不会知道发生了这样的变化。
之前描述的一些点尽管让人懊恼但尚可接受,但这种可靠性上的缺失最终导致我们决定放弃 Flow。一个类型检查器的根本目的就是要给我们信心,保证我们的代码中不会存在类型方面的问题。但是当我看到代码中存在明显的错误时,Flow 还是在欢快地宣布 Noerrors!
。我给我的代码增加类型注解,代码变得难以读懂,最后 Flow 却当做没看到。
与其这样没有安全感,我宁愿完全不要类型。本来我们都准备放弃在 JavaScript 中寻求类型支持了,但刚好那时 TypeScript 2 发布了,于是我们做了一些尝试并决定从 Flow 切换到 TypeScript。
切换时的一些技巧
当初利用 Flow 使我们的代码类型化的努力并没有白费。Flow 和 TypeScript 在语义和句法上 都非常非常相似。按照兼容 Flow 的方式写出来的代码同样也可以在 TypeScript 中很好地工作。主要的转换工作有:
重命名文件为
.ts
删除文件头部的
// @flow
做一些简单的语法调整(模块引入、接口)
修正 Flow 漏报的一些类型错误
我们使用 webpack,代码中使用了 ES6 特性(借助 Babel-Loader)和 ES6 模块(借助 Webpack2)。TypeScript 完全支持这些东西。 在工程配置上的一些变化有:
在 webpack 解析器配置中的
.js
和.jsx
前面增加.ts
和.tsx
增加
awesome-typescript-loader
创建一个
tsconfig.json
配置文件,打开--strictNullChecks
和--noImplicitAny
选项。如果你设置你的 typescript 编译目标是es5
,那你需要把lib
设置为["dom","es5","es2015","es2016","es2017"]
。 这样可以避免出现像Promise
这类东西没有定义的错误。
回顾我的其它文章:
以上是关于Flow vs. Typescript的主要内容,如果未能解决你的问题,请参考以下文章
T | 如何将三万行代码从 Flow 移植到 TypeScript?
如何将三万行代码从 Flow 移植到 TypeScript?