+
+
=
@spyl94 ~ Aurélien David
Pour se simplifier la vie...
// Typed
function foo(x: string, y: number): number {
return x.length * y;
}
// Not typed
function foo(x, y) {
return x.length * y;
}
😀
😒
Selon moi si un des points suivants est vrai:
Lecture
➡️️
Analyse
➡️️
Vérification
➡️️
✅ / ❌
🚀 la barrière d'entrée est très faible pour une codebase existante
⚠️️ Je n'ai testé que Flow
// .babelrc
{
"presets": ["react", "flow", "latest"],
}
En global:
$ brew install flow
Ou au sein de votre projet:
$ yarn add --dev flow-bin
$ yarn add babel-preset-flow
✂️️ La preset flow permet de supprimer les annotations lors du build
// index.js
// @flow
(function() {
function foo(x: string, y: number): string {
return x.length * y;
}
foo('Hello', 42);
});
$ flow # Démarre un serveur flow si besoin, puis affiche les erreurs:
index.js:6
6: return x.length * y;
^^^^^^^^^^^^ number. This type is incompatible with the expected return type of
5: function foo(x: string, y: number): string {
^^^^^^ string
4. On vérifie qu'on a bien une erreur en lançant flow:
$ yarn global add flow-typed
Les définitions de bibliothèques (libdef) décrivent les interfaces et les types utilisés par les librairies
$ yarn install # Install your dependencies
$ flow-typed install # Generates a `flow-typed` directory containing libdefs
# flow-typed/npm/is-url_v1.x.x.js
declare module 'is-url' {
declare module.exports: (url: string) => boolean
}
Example de libdef pour le package is-url
flow-typed est un outil permettant d'installer facilement les libdefs
🤓 Evidemment, vous pouvez également écrire les vôtres!
# .flowconfig
[ignore] # Files not visible
# ignore compiled files
.*/public/*
# ignore module source to prefer libdefs
.*/node_modules/*
.*/bower_components/*
# large dirs that are not imported
.*/vendor/*
[libs] # Directory containing libdefs
flow-typed
[options]
all=false # Only files annoted by @flow are checked
$ touch .flowconfig
$ flow ls # Liste les fichiers visible, pratique pour configurer la section [ignore]
emoji=true # Activer cette option pour ajouter des emojis aux réponses du serveur \o/
all=true # Sur une nouvelle codebase pour éviter d'avoir à annoter avec @flow
Excellent support sur Nuclide
let array: Array<number> = [1, 2, 3.14, 42];
let theAnswer: number = array[3]; // 42
let offTheEnd: number = array[100]; // No error
let tuple: [string, number, boolean] = ["foo", 0, true];
Tableaux :
Maybe:
// '?' allow null and undefined
var o: ?string = null;
type Rank = 0 | 1 | 2 | 3 | 4 | 5;
Union:
let object: {foo: string, bar: number} = {foo: "foo", bar: 0};
// Property writes must be compatible with the declared type.
object.bar = "bar"; // Error: "This type is incompatible with number"
let coolRating: {[id:string]: number} = {};
coolRating["sam"] = 10;
coolRating["paul"] = "cool"; // Error: "This type is incompatible with number"
// Optional properties
var optObj: { a: string; b?: number } = { a: "hello" };
// Burger syntax
type User = { name: string, age: number }; // allow extra properties
type StrictUser = {| name: string, age: number |}; // disallow extra properties
let object: Object;
let a: any;
let mix: mixed;
function greatestCommonDivisor(a: number, b: number): number {
if (!b) {
return a;
}
return greatestCommonDivisor(b, a % b);
}
// arrow functions
const double = (num: number): number => num * 2;
Fonctions ordinaires, asynchrones, promesses et générateurs sont supportés !
// async functions
async function getFriendNames(
friendIDs: Promise<number[]>,
getFriendName: (id: number) => Promise<string>,
): Promise<string[]> {
var ids = await friendIDs;
var names = await Promise.all(ids.map(getFriendName));
return names;
}
I wouldn't expect there to be further changes to PropTypes. Flow has become much more mature recently, and from what I heard from the React team, it is the longer term solution to type checking.
~ @gaeron
isRequired est équivalent à dire non null...
🙄 Comment gérer les cas où null est possible ?
PropTypes n'est utile qu'au runtime
🙄 Résultat non intégré à l'IDE !
// @flow
const Greeter = React.createClass({
propTypes: {
name: React.PropTypes.string.isRequired,
},
render() {
return <p>Hello, {this.props.name}!</p>;
},
});
<Greeter />; // Error: Missing `name`
<Greeter name={null} />; // Error: `name` should be a string
<Greeter name="World" />; // Error: "Hello, World!"
✅ Support par défaut des PropTypes
💪 Plus besoin d'exécuter notre code pour voir nos erreurs de passage de props !
type Props = {
title: string,
visited: boolean,
onClick: () => void,
};
class Button extends React.Component {
props: Props;
static defaultProps: { visited: boolean };
constructor(props: Props) {
super(props);
this.state = { display: 'static' };
}
render() {
/* ... */
}
}
type Props = {message: string};
function SayAgain({ message }: Props)
{
return (
<div>
<p>{message}</p>
<p>{message}</p>
</div>
);
}
React.Component
Stateless functional components
Les Higher-order components sont également supportés!
type Action = IncrementAction | DecrementAction;
export const reducer = (state: State = initialState, action: Action): State => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
Typer un reducer
3. Décrire le reducer
// @flow
type State = number;
const initialState: State = 42;
2. Décrire chacune des actions
type IncrementAction = { type: 'INCREMENT' };
type DecrementAction = { type: 'DECREMENT' };
export const increment = (): IncrementAction => ({ type: 'INCREMENT' });
export const decrement = (): DecrementAction => ({ type: 'DECREMENT' });
// @flow ./redux/vote.js
import type { Dispatch, Action } from '../types'; // Cf prochain slide
// ...
export const deleteVote = (dispatch: Dispatch): void => {
fetch('...').then(() => {
dispatch({ type: 'DELETE_VOTE'});
});
};
export const reducer = (state: State = initialState, action: Action): State => { /* */ }
3. On importe les types Action et Dispatch, pour décrire notre reducer et les fonctions du module
Typer plusieurs reducers (1/2)
1. On décrit à nouveau le state du reducer ainsi que ses actions
2. Cette fois, on exporte son state et la liste de ses actions
// @flow ./redux/vote.js
type VoteValue = 1 | 0 | -1;
type AddVoteAction = { type: 'VOTE', value: VoteValue };
type DeleteVoteAction = { type: 'DELETE_VOTE' };
export type State = { vote: ?VoteValue };
export type VoteAction = AddVoteAction | DeleteVoteAction;
import type { Store as ReduxStore, Dispatch as ReduxDispatch } from 'redux';
export type Store = ReduxStore<State, Action>; // Application store
export type Dispatch = ReduxDispatch<Action>; // Dispatch only accept type Action
Typer plusieurs reducers (2/2)
1. On importe le type State de nos différents reducers afin de définir le State global de notre application
// @flow ./types.js
import type { State as CounterState, CounterAction } from './redux/counter';
import type { State as VoteState, VoteAction } from './redux/vote';
export type State = {
counter: CounterState,
vote: VoteState
};
2. On décrit la liste de toutes les actions possibles
export type Action =
CounterAction |
VoteAction |
;
ℹ️️ On l'utilise pour typer le paramètre action dans nos reducers
3. On décrit notre store et notre dispatcher
// @flow components/Content.js
import React from 'react';
import { connect, type Connector } from 'react-redux';
import type { State, Dispatch } from '../types';
type Props = {| // Use exact object type for Props
count: number,
increment: () => void
|};
const Content = ({ count, increment }: Props) => (/* ... */);
const mapDispatchToProps = (dispatch: Dispatch) => ({
increment: () => { dispatch({ type: 'INCREMENT'}) }, // Ou un action creator
});
const mapStateToProps = (state: State) => ({ count: state.counter });
// Connector<OwnProps, Props>
const connector: Connector<{}, Props> = connect(mapStateToProps, mapDispatchToProps);
export default connector(Content);
Typer les composants connectés
✅ Vérification des props compatible avec `connect`
✅ Une props inconnue = ❌
✅ Dispatcher une action inconnue = ❌
ℹ️️ Pas besoin de rendre explicite le type de retour de notre composant, Flow le déduit parce qu'il comprend JSX. (Type deviné: React$Element<any>)
Flow est pratique pour analyser le code pendant qu'on l'écrit mais il ne peut prédire les types lors de l'exécution réelle du code, comme le résultat d'une requête à une API REST.
type Person = {
name: string;
};
function greet (person: Person): string {
return 'Hello ' + person.name;
}
import t from 'flow-runtime';
const Person = t.type('Person', t.object(t.property('name', t.string())));
function greet(person) {
let _personType = Person;
const _returnType = t.return(t.string());
t.param('person', _personType).assert(person);
return _returnType.assert('Hello ' + person.name);
}
t.annotate(greet, t.function(t.param('person', Person), t.return(t.string())));
➕
⬇️️
$ yarn add --dev eslint-plugin-flowtype
{
"parser": "babel-eslint",
"plugins": [
"flowtype"
],
"rules": {
'flowtype/boolean-style': ['error', 'boolean'],
'flowtype/define-flow-type': 1,
'flowtype/delimiter-dangle': ['error','never'],
'flowtype/generic-spacing': ['error', 'never'],
'flowtype/no-primitive-constructor-types': 'error',
'flowtype/no-weak-types': ['error', {'any': false, 'Object': false }],
'flowtype/object-type-delimiter': ['error', 'comma'],
# ...
}
}
Ajouter le support de Flow à notre linter
❤️️ Pratique pour éviter d'abuser des types faibles
⚠️️ Ne pas être trop strict lorsqu'on part d'une codebase existante
🙁 Pas de support de Flow dans le parser par défaut
$ flow coverage app/js/store.js
Fichier par fichier:
$ flow-coverage-report -i "app/**/*.js"
Application:
C'est la mesure de qualité de votre analyse statique
Objectif recommandé: viser > 90% de couverture
// package.json
{
"scripts": {
"typecheck": "flow check"
}
}
$ yarn run typecheck # Add this !
On travaille dur pour mettre à jour la démocratie... Vous nous donnez un coup de pouce ? 🙏
Slides: bit.ly/reactplusflow
Démo de React + Redux + Flow: spyl94/react-brunch-demo