Flow and TypeScript are both fantastic projects with a very noble and ambitious mission: to add static types to JavaScript. After working with the both of them extensively, I've come to the conclusion that, to no offense to the very many smart people working on Flow, there's little reason to choose it over TS for any new project going forward.
Reasons why I believe TS should be chosen over Flow:
- TS offers significantly more available type definitions for third-party libraries
- While the validity of this may depend on one's choice of libraries, I've never personally seen a project which had more Flow definitions than TS definitions available for its dependencies.
- TS offers very powerful features in its type system that are missing from Flow, like conditional types and argument spread types
- TS gives a stronger developer experience:
- Refactors:
- Go to definition (also available with Flow, but slower and doesn't always work)
- Extract to new file
- One-button project-wide renaming of anything: classes, types, variables, methods, properties, even string constants
- Automatic imports, from autocomplete and as a quick fix
- Organize imports, sorts and removes unused
- While these are considered features of the editor, support for all of them is built-in to the TypeScript server itself, meaning any editor with support for TS is likely to have these capabilities as well. The same can't be said for Flow, where a lot more legwork has to be done by the editor instead of the type check server.
- Refactors:
- TS's server is much faster and generally more reliable than that of Flow
- This one has some mixed answers, but the general consensus does seem to be that TS is faster, especially on Windows.
- Notably, Flow doesn't perform a full type check on a file until it's saved, where TS will treat every open file in the editor as if it were saved, giving more useful, immediate feedback. I've seen some people state otherwise, but this has never been the case for me.
Some commonly-noted advantages of Flow and why they're not very strong or even invalid:
- Integrates better with Babel
- With Babel 7 comes a preset that strips types from TypeScript code the same way as Flow. A few features are not supported completely, as there's no ES equivalent (
const enum, namespaces,export =) but in my opinion, these features shouldn't be used in modern code anyway
- With Babel 7 comes a preset that strips types from TypeScript code the same way as Flow. A few features are not supported completely, as there's no ES equivalent (
- Flow is "Closer" to JavaScript than TS is / TS is just JS for C# people
- This may have been true in TS's early days, but TS for the most part really is just static typing on top of JavaScript, ever since a lot of ES features have been officially implemented into the spec. TS does still offer its own specific syntaxes (constructor shorthands, enums) but they're minimal, and useful at times. They can be avoided or banned through a linter if one desires.
- Easier to add into an existing JS project
- TypeScript adds the
// @ts-checkannotation, as well as"allowJs"and"checkJs"flags for this purpose, but only JSDoc syntax can be used in JS files for typechecking.
- TypeScript adds the
- Flow has a stronger, smarter type system. It can infer more without annotations.
- Common example:
const square = n => n * n; square('1')will throw an error without a type annotation onn, saying that a string can't be used with* - In real-world usage, this advantage doesn't apply very often:
- Flow requires exported functions/methods to have type annotations. In a project using ES modules (basically every modern project), you'll end up with as many annotations as you would with TS anyway. A little less, but not substantially.
- More type annotations means code that's documented better, and errors are caught sooner, so needing less annotations isn't much of an upside when maintainability is a priority.
- This advantage is valid for untyped third-party code, however:
- A type system can only do so much inference with untyped code, so you won't get very much help in Flow or TS depending on the library's complexity.
- If the library is not complex (just one or a few functions, for example) writing your own simple definitions shouldn't take too much time
- Common example:
- TS's flag-based configuration is confusing
- Both TS and Flow have their own issues and nuances with configuration; Flow is no saint here. No fault to either project, this is the result of attempting to add a type system to a very dynamic language with a ton of applications, use cases and setups.
Valid faults to TS / Flow advantages:
- TS's module resolution logic is confusing
- I've seen lots of confusion revolving around how to write imports or exports, e.g. whether to write
import Reactorimport * as React, whether to writeexport defaultorexport =. The answer depends on the tsconfig setup, how the library's definitions are written, and relies on the library and type definitions both being written/transpiled correctly. How to do so correctly isn't well documented, as far as I can tell. It's common to run into cryptic errors when something is done wrong but doesn't seem wrong at all, e.g. havingexport const ...andexport =in the same file. This doesn't seem to be an issue with Flow. Hopefully TS can improve on this front going forward. For now,"esModuleInterop": truesolves a lot of issues relating to this.
- I've seen lots of confusion revolving around how to write imports or exports, e.g. whether to write
- Flow is strict by default, only in TS through a flag
- Even though turning on the flag isn't difficult (it's on by default when making a project with
tsc -init) it would still be nice to have it on by default in TS. As far as I know, it has to stay this way for backwards compatibility
- Even though turning on the flag isn't difficult (it's on by default when making a project with
If you feel that any of this is not accurate in any way, change my mind. I also want to keep this update as the two projects evolve over time; some of this is bound to change.