Flow Compared To Typescript

Disclaimers:

If you haven’t noticed, there are two popular ways of adding types to your Javascript code — Microsoft’s Typescript and Facebook’s Flow.

Both are extremely powerful, gradual, structural, type-systems. They also, look very similar, with almost the same syntax. Almost.

Often developers jumping from one system to the other get frustrated that all the things they learnt no longer work in the same way. And they assume that the features aren’t supported. And so, they do what any modern JS developer would do and vent on Twitter.

But wait, didn’t I just tell you that both type-systems are powerful. Everything you did in one type-systems can probably be achieved in the other.

So, this is an ongoing, incomplete comparison of some of the differences between Flow and Typescript

Interfaces vs Object Types #

It is common in Typescript to use interface to declare the type of an object.

interface MyObj {
  a: number;
  b: string;
}

In Flow you’re more likely to do something like this:

type MyObj = {
  a: number;
  b: string;
}

But wait, did you know that that both those examples are valid in both Flow and Typescript??
Mind-Blown

You see, flow supports interface and Typescript supports object types. So, it’s usually a matter of culture.

class #

Both flow and typescript support adding types to a class

class X {
  a: number;
  b: string;
}

However, there is one big difference. Flow doesn’t support implementing Interfaces.

So, this code while valid in typescript isn’t valid in Flow:

interface AData {
  a: number;
  getA(): number;
  setA(val: number): void;
}

class AB implements AData {
  a: number = 0;
  getA(): number {
    return this.a
  }
  setA(v: number) {
    this.a = v
  }
}

Now in typescript any object created by instantiating the class AB will also be of type AData. (Java 101).

In Flow, you can’t actually use the implements keyword. So how do you do the same thing with Flow? You remove the words implements AData and you’re done.

You see, since the class has the same structure as the interface defined before, you can use an object of its type whenever the interface type is expected.

You do lose the ability to type-check your class definition as you type it, but you’ll catch the same mistakes when you actually use it.

That said, if you’re just declaring types, flow lets you use a different keyword mixins to share common code. You can read about it here

public/private etc etc. #

Flow also doesn’t support any of the other extensions to class syntax that come with Typescript. You see Typescript often claims to be JS with types, but it also brings all these other syntax extensions that have basically nothing to do with JS but look familiar to Java and C# developers. So, you get things like the public and private keywords. I already mentioned implements

Flow skips all these syntax extensions and only supports features in ES6 and many proposals supported by ES7. A simple rule is that if Babel supports a feature, Flow probably supports it too.

Compilation #

Flow is not a compiler, Typescript is. You will usually use Typescript instead of Babel. You will usually use Flow alongside Babel. Again, Flow is much more literally just Javascript with types. Flow understands many of the new, fancy ES6+ features, but all it does is check your code like a super-smart linter. You still rely on Babel to convert your code down to ES5, and also remove your types. Typescript pretty much does it all.

On the other hand, both Flow and Typescript kind of support type-checking without any type annotations at all. Typescript understands JSDoc annotations and do its best with just those types.

Flow, has a special flotate syntax that lets you write ALL your annotation in inline comments, and skip the compilation step entirely. Learning this syntax is easy. Whenever you’re about to write an inline annotation, you can just wrap that in a comment.

var a: number = 1
// becomes
var a/*: number */ = 1

And any other multi-line type definitions etc, can be wrapped in: /*:: ... */

/*::
type MyObj = {
  a: string,
  b: number
}
*/

Enums #

Typescript supports enums, Flow doesn’t. Flow just supports enum types.

Typescript:

enum States {
  ON,
  OFF,
  QUANTUM
}

Flow:

type States
  = 'ON'
  | 'OFF'
  | 'QUANTUM'

Actually, the enum type is supported by Typescript as well. From what I gather, the only point of an enum is to let you define descriptive variable names while using small numbers. So, in the first example, States is just a collection of 0, 1 and 2, but you get to use names that actually mean something.

Again, Flow skips this, because it doesn’t add features to the language, just types. However, you can achieve something similar with Flow with a little more typing.

const ON = 0
const OFF = 1
const QUANTUM = 2
type States = 0 | 1 | 2

It’s a little more code, but it does exactly the same thing as a Typescript enum

Magic Types #

By now, you’re probably thinking “Typescript is great, who needs Flow, Bleh!”. Let me even the odds. In this section that should probably be called, ‘Getting derailed while comparing type systems and deep-diving into a little-known feature instead’

Flow has a few Magic Types that make it possible to write much more accurate types.

$Keys #

It’s a magic type that takes an object type and gives you an enum of all the keynames.

Let’s consider a Record type. A function that takes an object, and then returns an object with getters for all the values. Consider it a simple way to make objects immutable.

function makeRecord(obj) {
  return {
    get(key) {
      return obj[key]
    }
  }
}

With Typescript you’d probably type it like so:

function makeRecord(obj: Object) {
  return {
    get(key: string): any {
      return obj[key]
    }
  }
}

But you see how yo basically lost all type information with the get method.

You can do slightly better with Flow.

function makeRecord<T: Object>(obj: T) {
  return {
    get(key: $Keys<T>): any {
      return obj[key]
    }
  }
}

Now, flow will complain if you use a string that didn’t exist in the original object.

The value type is still essentially lost, but that’s something that might be fixed soon. The Typescript team on Twitter suggested that they will add the functionality soon too.

$Diff #

This is a magic type that was basically added for React, but it can be used for other purposes as well.

In basic terms, it’s object subtraction.

type A = {
  a: number,
  b: string
}

type B = {
  b: string
}

type C = $Diff<A, B>

// {a: number, b?: string}

It’s used to to create the prop-contract for React components by doing $Diff<Props, DefaultProps>.

The Typescript type-definition just skips defaultProp entirely as it can’t be supported.

This magic type is also extremely useful for defining the types for Higher-Order-Components in React, but that is a complex topic probably left for another post.

Inferrable Types #

This is not a Magic Type per-say, but it makes it possible to write magic types. In flow you can use the * character to tell Flow that it should try to infer the type on its own, and it can be extremely smart.

Here’s an example of using * to write your own magic type. This code is a little far-fetched and you won’t usually need to write it. But when you do need it, nothing else will do.

type $Function1<A, B> = (arg: A) => B;
type _Function1Value<A, B, F: $Function1<A, B>> = B; // eslint-disable-line
type $Function1Value<F: $Function1<*, *>, A> = _Function1Value<A, *, F>;

// This is a type that takes a function that takes one argument, the type of the argument and gives you the return type. This is useful when dealing with function overloads.

TypeCasting #

This is just a simple syntax difference. When you deal with types, every now and then you need to override the type system and tell it what the type of something is.

In Typescript you do it like this.

(<TypeName> variableName)
// OR
(variableName as TypeName)

[you’ll need to use the second syntax if you plan on using JSX]

In Flow, you do it like this:

(variableName: TypeName)

Library Type Definitions and Tooling #

There’s no simpler way to say this. Typescript is a more mature tool and you’ll find better IDE integrations and more types for libraries.

However, Flow is pretty good with it’s IDE features too. In the department Library definitions, however, Flow has a long way to catch up.

Conclusion #

I’ve probably missed a lot of stuff. But I think I covered the basics. Let me know if I got anything wrong, or missed something important. Any general feedback is also welcome. (@naman34)

There are a few concepts I skipped on purpose, such as co-variance vs contra-variance. This is because I’m not sure about all the details about that. I just know Flow and Typescript have different strategies for dealing with sub-types.

 
192
Kudos
 
192
Kudos

Now read this

Replacing Eval with a Web Worker

Eval is Evil. Douglas Crockford has made that so popular that any competent Javascript developer thinks twice before actually using eval. But when do we even need to use eval? Sometime we need to actually affect the webpage by evaluating... Continue →