-
Lots of type inference
-
Null/undefined checking
Similar syntax
As an example, I ported my [EventEmitter](https://github.com/voltrevo/voltrevo-event-emitter) module to [Typescript](https://github.com/voltrevo/voltrevo-event-emitter-ts) and [Flow](https://github.com/voltrevo/voltrevo-event-emitter-flow) and they are almost identical.-
Does a lot for you, especially when paired with VS Code, so your project requires less set-up
-
Type annotations available for many more third party libraries than Flow
Type information on hover is complete
```js class Actor { act(description: string) { return { undo() { console.log(`undo ${description}`); }, }; } }const actor = new Actor();
// Typescript says that action
is a { undo: () => void }
, flow just says {}
const action = actor.act('foo');
// Strangely though, flow does know that undo
is a () => void
const undo = action.undo;
In general, VS Code is also able to provide a much better intellisense experience than any Flow plugin available for Atom/Sublime/VS Code. Flow seems similarly capable under the hood though, so it should get better.
</details>
<details>
<summary>Type inference works with generics</summary>
```js
function identity<T>(value: T) {
return value;
}
// Typescript knows that foo is a string. Flow does not. Flow also emits an error if you try to
// add a string type annotation to foo.
const foo = identity('foo');
Better support for 100% typed code via `--noImplicitAny`
```js // While Flow sometimes asks for type information, e.g. for parameters of exported functions, // there are many cases where the `any` type can sneak into your code and prevent type checking. // A common one is missing an index signature of an object:const map = { foo: 'bar' };
function get(key: string) {
// Typescript: Element implicitly has an 'any' type because type '{ foo: string; }' has no index
// signature.
// Flow: Exports get(key: string): any
without warning.
return map[key];
}
However, much of this gap can be closed by using [flow-coverage-report](https://www.npmjs.com/package/flow-coverage-report):
$ flow-coverage-report -i 'src/.js' ┌───────────────────────┬─────────┬───────┬─────────┬───────────┐ │ filename │ percent │ total │ covered │ uncovered │ │ src/Collection.js │ 100 % │ 38 │ 38 │ 0 │ │ src/MapWithDefault.js │ 100 % │ 28 │ 28 │ 0 │ │ src/index.js │ 100 % │ 59 │ 59 │ 0 │ │ src/mapTests.js │ 87 % │ 8 │ 7 │ 1 │ │ src/useMap.js │ 75 % │ 4 │ 3 │ 1 │ └───────────────────────┴─────────┴───────┴─────────┴───────────┘ ┌─────────────────────────┬──────────────────────────────────────────┐ │ included glob patterns: │ src/.js │ │ excluded glob patterns: │ node_modules/** │ │ threshold: │ 80 │ │ generated at: │ Mon Nov 07 2016 16:02:09 GMT+1100 (AEDT) │ │ flow version: │ 0.34.0 │ │ flow check passed: │ yes (0 errors) │ └─────────────────────────┴──────────────────────────────────────────┘ ┌─────────────────────────────┬─────────┬───────┬─────────┬───────────┐ │ project │ percent │ total │ covered │ uncovered │ │ voltrevo-event-emitter-flow │ 98 % │ 137 │ 135 │ 2 │ └─────────────────────────────┴─────────┴───────┴─────────┴───────────┘
Some may actually prefer this to ease the transition into adding typing to a project. On the other hand, others may prefer temporarily turning on Typescript's `--noImplicitAny` to view errors in the editor which would give them feedback in real-time as they add type information.
</details>
<details>
<summary>Correctly identifies `.pop` on a `T[]` as a `T | undefined`</summary>
```js
const nums = [17];
nums.pop();
const num = nums.pop();
// Typescript correctly emits an error here because it has inferred `num` as a `number | undefined`.
// Flow doesn't catch it because it infers `num` as `number`.
console.log(num.toString());
Supports interfaces
```js interface X {} interface Y {}// Typescript: OK. Flow: Error: implements not supported. class Widget implements X, Y {}
Flow can parse the `interface X { ... }` syntax, but without `implements` it seems equivalent to just defining a type (and you'll need [babel-plugin-transform-flow-strip-types](https://www.npmjs.com/package/babel-plugin-transform-flow-strip-types)).
</details>
## Advantages of Flow
- Provides type checking only, so it is more flexible about how you set-up the rest of your project
<details>
<summary>Catches the error when you assign a `Derived[]` to a `Base[]`</summary>
```js
class Animal {}
class Cat extends Animal {
furColor: string;
}
const cats: Cat[] = [];
const animals: Animal[] = cats;
// Flow correctly emits an error here. Typescript does not. It's an error because it makes cats
// no longer conform to type Cat[].
animals.push(new Animal());
// As an example, badFurColor below is inferred as a string, and furColor is
// provided via autocomplete, even though cats[0] is not a cat.
const badFurColor = cats[0].furColor;
Infers types based on usage (not just known input -> operation -> inferred output)
```js function double(x) { return x * 2; // Flow infers x as string because of usage below, and emits an error here }const result = double("foo");
**Note**: With `noImplicitAny`, Typescript emits an error for `x` being implicitly the `any` type.
</details>
<details>
<summary>Doesn't confuse `{}` with `string` and `number`</summary>
```js
// Flow (arguably) correctly emits an error saying string is incompatible with object.
// Typescript doesn't mind.
const foo: {} = 'foo';
// There is a Typescript bug related to this that causes it to emit an error for this code,
// Flow handles it fine though:
const items = ['foo', {}, null];
for (const el of items) {
if (typeof el === 'string') {
console.log(el.toUpperCase());
}
}
More information: microsoft/TypeScript#12077
Mistypes elements of `T[]` accessed via subscript as `T`. They should be `T | undefined` due to the out of bounds case
```js const nums = [1, 2, 3]; const foo: number = nums[100];console.log(foo.toString()); // boom not prevented
</details>
<details>
<summary>Can't declare types in class scope</summary>
```js
class Foo<T> {
type Wrapper = { value: T }; // Both error: 'type' not expected
}
Doesn't catch uninitialized member variables
```js class DefaultGetter { generateDefault: () => string;get() {
// Both typescript and flow don't catch that generateDefault
is undefined
return this.generateDefault();
}
}
const defaultGetter = new DefaultGetter(); const foo = defaultGetter.get();
</details>
<details>
<summary>Allows index signatures with non-nullable value types</summary>
```js
const map: { [key: string]: string } = {
foo: 'bar',
};
const key = 'idontexist';
// Both Typescript and Flow fail to catch this
const oops = map[key].toUpperCase();
This is similar to the array subscript issue. In both cases it shouldn't be possible to have the subscript operator return a non-nullable type, because arrays and objects are unable to define a value for all possible subscripts.
- In Typescript, the type of
undefined
isundefined
, in Flow the type ofundefined
isvoid
@Kriegslustig
T | undefined
, I didn't realize Flow usesvoid
instead ofundefined
for the typename, soT | void
is the equivalent. I removed the point and added a new one under 'Other Notable Differences', thanks!So
--noImplicitAny
catches both cases, but Flow's approach only gets the one with the statically determined key. However, if you're not using the subscript operator then an index signature shouldn't be used, and both Typescript and Flow will treat it as an object and ensure you don't accidentally use a property that's not there: