Typed JavaScript
Flow & TypeScript
Types In JavaScript
- Values and expressions have types
typeof 'str'; // 'string'
const variable = 3;
typeof variable; // 'number'
let variable; // What will the type be? ¯\_(ツ)_/¯
- Variables don't have types
Typed JavaScript
- Variables can have types
// `value` is a 'number'
const value: number = 3;
// `add` takes two 'number' arguments
// and returns a number
function add(a: number, b: number): number {
return a + b;
}
Competing tools
TypeScript & Flow
TypeScript
- www.typescriptlang.org
- By Microsoft. Used by Angular 2, Cycle.js, ...
- Compiler: JavaScript superset to JavaScript
TypeScript
- Adds: Types, Enums, public/private class methods, JSX...
- Incompatible with Babel, and not extendable.
- .ts extension: incompatible with some tools (AVA, ESLint)
TypeScript
- Project must entirely be in TypeScript
- Will not build if there are errors / missing type definitions
- Most popular, with the most package definitions
- DefinitelyTyped
- Try it online: www.typescriptlang.org/play/index.html
Flow
- https://flowtype.org
- By Facebook. Used by React, Jest...
- Static type checker
Flow
- Understands JavaScript with types syntax
- Compatible with Babel, and thus extendable.
- .js extension: compatible with some tools (AVA, ESLint)
- Babel plugin to remove types from code
Flow
- Types can be added incrementally to the project
- Only files with `// @flow` will be linted
- Not that many package definitions
- flow-typed
- Try it online: https://flowtype.org/try/
Which one could we use?
- Flow!
- Hard to move our codebase to TypeScript all at once
Type syntax
- Basic type syntax is the same in both tools
- We'll discover Flow's syntax, stricter than TypeScript's
Basic types
// number
const a: number = 3;
// string
const b: string = 'foo'
// boolean
const c: boolean = true;
// null
const d: null = null;
// void (undefined)
const e: void = undefined;
Types in expressions
// In functions
function foo(
a: string = defaultValue,
b?: number // optional parameter
): boolean {} // return type
// Objects
const object
: {a: string, b: number}
= {a: "foo", b: 0};
// Destructuring
const [a: number] = bar;
const {a: number = 2} = bar;
General base types
let bar: any;
const foo: any = bar;
foo.z()[0].bar.yolo + '!!!'; // No checks
function parseInt(value: mixed): string {
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number') {
return String(value);
}
return '0';
}
- any: can be anything, and no checks will be made on it.
Modules without types will be considered as type any)
- mixed: can be anything, but checks will be needed to use value safely
Function types
function readFile(path: string, cb: Function): void {
}
// More precise
function readFile(
path: string,
cb: (err: Error, content: string) => void
): void {
}
- Function: Takes any number of parameters, returns `any`
Objects
const obj: Object = {};
obj.foo; // considered as 'any'
// Loose (can have additional properties)
const obj
: { foo: string
, bar?: number // bar is optional
}
= { foo: 'yolo'
, bar: 123
, baz: true
};
// Strict (can't have additional properties) using {| |}
const obja
: {| foo: string
, bar?: number // bar is optional
|}
= { foo: 'yolo'
, bar: 123
, baz: true // Not allowed
};
- Object: returns`any` for any property
Generic types
const numberArray: Array<number> = [1, 2, 3]; // Also available as number[]
const userP: Promise<Object> = getUser('Jeroen');
Type aliases
// user.js
type UserId = string;
export type User = {|
id: UserId,
name: string
|};
function getUser(id: UserId): Promise<User> {
// ...
}
// greeting.js
import type {User} from './user';
function sayHiToUser(user: User): string {
return `Hello ${user.name}`;
}
Union types
let PORT: string | number = '3000';
PORT = 3000;
type User =
| AnonymousUser
| RegisteredUser
;
type CardColor =
| 'Heart'
| 'Clover'
| 'Diamond'
| 'Spade'
;
type Card = {
color: CardColor,
number: number
};
const card: Card = {
color: 'Spade',
number: 1
};
Intersection types
type Foo = {a: string} & {b: string}
const foo: Foo = {a: 'foo', b: 'bar'};
import type {Request} from 'express';
type CoorpRequest =
& Request
& { logger: Function
, currentUser: User
}
Model data
type User = {|
id: UserId,
name: string,
// ...
|};
type Discipline = {|
ref: string,
name: string,
modules: Module[]
// ...
|};
Prop types for components
type Props = {
rating: ?number,
maxRating: ?number,
linkBuy: ?string,
linkTry: ?string,
author: {
name: ?string,
socialLinks: SocialLinks[]
}
};
const DisciplineRightaside = (
props: Props,
// Tuple: array of size 0 (no children)
children: []
) => {
}
const conditions = checker.shape({
props: checker.shape({
rating: checker.number.optional,
maxRating: checker.number.optional,
linkBuy: checker.string.optional,
linkTry: checker.string.optional,
author: checker.shape({
name: checker.string.optional,
socialLinks: checker.array.optional
}).optional
}),
children: checker.none
});
Model data à la Elm
const initialModel: Model = 0;
const update = (
model: Model = initialModel,
action: Action
): Model => {
switch (action.type) {
case 'increment': {
return model + 1;
}
case 'decrement': {
return model - 1;
}
case 'reset': {
return 0;
}
}
return model;
};
type Model = number;
type Increment = {|
type: 'increment'
|};
type Decrement = {|
type: 'decrement'
|};
type Reset = {|
type: 'reset'
|};
type Action =
| Increment
| Decrement
| Reset
;
If you want to change your model, Flow will tell you where you missed stuff
Make impossible states impossible
Richard Feldman - "Making Impossible States Impossible"
type Survey = {
questions: Array<string>,
answers: Array<?string>
};
const survey: Survey = {
questions: [
"What is the color of Henry IV's white horse?"
],
answers: [
'White',
'Barack Obama'
]
}
// WTF happened here?
Make impossible states impossible
type Question = {
question: string,
answer: ?string
};
type Survey = Array<Question>;
const survey: Survey = [
{
question: "What is the color of Henry IV's white horse?"
answer: 'White'
}
];
Types all the way down
- Kris Jenkins - Types All The Way Down
- Send type info to front-end
- In our case: create a package with model typings and consume on the front-end
Add it to a project
- `npm i -D flow-bin && touch .flowconfig`
- npm test: `previous npm test && flow`
- Add Babel, use babel-plugin-transform-flow-strip-types
- With AVA: Add the same plugin to the babel
- ESLint: Use `babel-eslint` parser
- Example: api-platform
Dependency typings
- flow-typed (beta)
- Installs typings in a local folder
- Creates `any` for all the exported functions of packages not found
- Update, commit, contribute
Pros
- Static type checker that detects errors
- Safer refactoring
- Code easier to read by typing the data
- Model data types to avoid odd data states
- For libraries, makes the client code that uses Flow much safer and easier to use
Cons
- For libraries, makes client code that does not use Flow unsafer, as impossible arguments are not tested
- Learning curve. Some things are hard to model.
- Package definitions are rare, and often not up to date
Typed JavaScript
By Jeroen Engels
Typed JavaScript
- 757