Michal Zalecki
Michal Zalecki
software development, testing, JavaScript,
TypeScript, Node.js, React, and other stuff

TypeScript vs Flow

TypeScript calls itself a JavaScript superset and compiler while Flow is a static type checker. TypeScript is developed by Microsoft and highly inspired by C# or Java type systems. What I find interesting is that TypeScript is actually written in TypeScript. Flow is created by Facebook, written in OCaml. Most TypeScript annotations are compatible also with Flow so learning curve for switching from one to another is really smooth.

In my experience Flow is much less obtrusive than TypeScript. It can be added gradually to the project file by file. To enable type checking in flow add // @flow at the top of the file. Flow type annotations can be stripped down with one Babel plugin.

Tooling

When you using Flow you would like to use Nuclide as well. Nuclide supports Flow but it's sluggish addon for Atom which out of the box is not the most responsive editor. Still faster than any other full-blown IDE to be honest. Nuclide experience isn't nearly as good as Visual Studio Code with its IntelliSense.

When it comes to typings when I've published that article there was typings which allows for installing definitions from DefinitelyTyped repository (among the other sources) and nothing which would support you in looking for Flow type annotations. The result was you probably ended up with many any type annotations when using third-party libraries.

As 5 months in JS world is a long time, things has changed since then. Flow has now flow-typed. It's still true that it’s much harder to find good quality and up-to-date typings for Flow than for TypeScript. Meanwhile TypeScript 2.0 supports a new way to install typings with npm.

npm install -S @types/lodash

How cool is that!

Documentation

Both, TypeScript and Flow, documentations are good and complete. What I find annoying is that TypeScript takes too serious that it's a language superset. Basically TypeScript Handbook teaches you not only TypeScript but also JavaScript without making clear where JavaScript (ES2016) ends and TypeScript begins. Such approach can do more harm than good for less experienced JavaScript developers and more experienced probably don't need to read once again about difference between let and var. Flow docs is only about Flow and that's great.

Type Inference

Type Inference a.k.a. type checking for free. Flow is much better than TypeScript in inferencing types (TypeScript 2.0 update included). Following code is compiled by TypeScript without errors and in runtime result ends up being a NaN while Flow can catch the bug and no annotations required.

function double(x) {
  return x * 2;
}
const result = double("foo");
src/index.js:7
  7: const result = double("foo");
                    ^^^^^^^^^^^^^ function call
  4:   return x * 2;
              ^ string. This type is incompatible with
  4:   return x * 2;
              ^^^^^ number

TypeScript way for solving this issue is to use --noImplicitAny which will force adding type annotation to x and then type checking kicks in but it’s not "type checking for free" any more.

Type System

I'm only going to point out some differences which I consider relevant based on my experience wit TypeScript or Flow. For full reference documentation is always the best place to go.

Encapsulation

TypeScript supports encapsulation with public, private, protected modifiers and readonly since TypeScript 2.0. I'm not a huge fan of classes in JavaScript but it deserves to be pointed out. Flow doesn't really support encapsulation. What you can do is to enable munge_underscores options which is going to treat methods which starts with underscore as private.

class Greeter {
  private counter: number = 0;
  constructor(public name: string) {};

  hello(): string {
    return `Hello ${this.name} ${this.incrementedCounter()}. time!`;
  }

  private incrementedCounter(): number {
    return ++this.counter;
  }
}

Enums

Enums or actually literal types in Flow. I consider presence of enums in TypeScript as a huge con. Enums in TypeScript exists in runtime and they are sequent numbers. That's another TypeScript element which may lead to further JavaScript ecosystem fragmentation. Literal types in Flow are much nicer with use case of defining type which is only one of the specified values.

React and JSX

Most of the projects I'm working on are written in React so good JSX support and understanding of prop types is quite important for me. The only real difference is that prop types and state types are implemented using generic types in TypeScript and in Flow just by setting a type for instance property.

// TypeScript
class Foo extends React.Component<FooProps, FooState> {}

// Flow
class Foo extends React.Component {
  state: FooState;
  props: FooProps;
}

I've come across opinions that TypeScript is for Angular 2 and Flow for React (both build by Facebook). As it’s true that Flow works nicely with React, TypeScript being natural choice for Angular 2 doesn’t make using it with React any less beneficial.

Null and Object

Let's start with null. By default in TypeScript null is a subtype of all types. In Flow null is a distinct value and has it's own type, which is a good thing. Don't use null. Flow on the other hand supports maybe types but it's not what Maybe is i.e. in Elm. Flow maybe type includes null and undefined.

const name: string = null;  // TS: OK
const name: string = null;  // Flow: null is incompatible with string
const name: ?string = null; // Flow: OK

Flow is also a bit stricter and does not consider primitive types to be subtypes of Object.

const foo: Object = 1; // TS: OK
const foo: Object = 1; // Flow: number is incompatible with object type

TypeScript 2.0 caught up with Flow and when you use --strictNullChecks then null and undefined are not considered subtypes of any type.

// without --strictNullChecks
const foo: number = 1;         // OK
const foo: number = null;      // OK
const foo: number = undefined; // OK

// with --strictNullChecks
const foo: number = 1;         // OK
const foo: number = null;      // Type 'null' is not assignable to type 'number'.
const foo: number = undefined; // Type 'undefined' is not assignable to type 'number'.

Conclusion

After spending some time with TypeScript and Flow I can say that there is not clear winner. I agree that you should use TypeScript for writing libraries. Flow will be a better choice when it comes to adding typings to the existing project.

When it comes to choosing between TypeScript and Flow for a new project I'd consider how project benefit from having type annotations. If having typings everywhere is important go for TypeScript, it has much better typings available for third-party libraries. On the other hand when your team is not convined to TypeScript which is more and more different from JS or you just love ES6+ and want to write idiomatic/stage-0 JavaScript code go for Flow.

Photo by Noah Silliman on Unsplash.