Lengow.get('js').upgrade();

ES6, React, Redux, Webpack et les autres 🚀

JAVASCRIPT ! JAVASCRIPT ! JAVASCRIPT ! *

* légèrement édité

🤔 Pourquoi ?

Dis moi c'est où t'as mal ?

C'est grave docteur ?

  • Architecture
  • Maintenabilité
  • Qualité
  • Performances
You know, Services, Contrôleurs, Vues, etc.

Ah oui

The jQuery Effect™

Grooos fichiers


//bad
if ($('.toto').hasClass('is-active')) {
    $('.titi').hide();
} else {
    $('titi').show();
}
                    
  • JS
  • Salut DOM !
  • Donne moi .toto


  • Donne moi .toto.is-active


  • Donne moi .titi


  • ...
  • Browser
  • Salut gros !
  • Attends 100ms stp
    ...
    Voilà
  • Attends 100ms stp
    ...
    Voilà
  • Attends 100ms stp
    ...
    Voilà
  • ...

Solutions

  • Meilleur JS

    ES6, Webpack

  • Frameworks

    Angular, Ember, Redux*, etc.

  • * C'est pas un framework mais c'est pas grave

  • Templating

    React, Vue.js, etc.

  • Tests

    Mocha, chai

De quoi va-t-on parler ?

  • 1. JS like a boss

    ES6, λ

  • 2. React à la rescousse

    Le système de vue

  • 3. Redux

    reducer: (state, action) ⟼ state

  • 4. Outils de Pros®

    Webpack, ESLint, Tests

  • Démo

1. JS like a boss

ES6

Quelques petits trucs sympas* pour écrire du meilleur code.

* Parmi pleins d'autres

Cet homme est fou 😱
Les navigateurs ne vont pas supporter ça avant 2038 !
Pas d'inquiétude !
Webpack va nous permettre de faire du transpilling
ES6 → ES5 👌

ES6

  • Arrow function
  • const, let
  • Promise
  • modules et class
                
'use strict';

//ES5
[1, 2, 3].map(function (i) {
    return i * 2;
}); // [1, 4, 6]

//ES6
[1, 2, 3].map((i) => i * 2); // [2, 4, 6]
                
            
                
'use strict';

//ES6
const MyConstructor = function () {

    //this est MyConstructor

    this.arr = [1, 2, 3];

    return this.arr.map((i) => {
        //this est toujours MyConstructor
        return i * this.arr.length;
    });
};

MyConstructor(); // [3, 6, 9];
                
            
                
'use strict';

//ES5
for (var i = 0; i <= 21; i++) {
    var j = i * 2;
}
console.log(j); //42 ¯\_(ツ)_/¯

//ES6
for (let i = 0; i <= 21; i++) {
    const j = i * 2;
}
console.log(j); //ReferenceError: j is not defined 👍
                
            
                
'use strict';

//ES6
const a = 1234;
a = 5678;
//TypeError: Assignment to constant variable
                
            
                
'use strict';

//ES5
//callback hell
doSomethingAsync(function () {
    doSomethingElse(function () {
        maybeSomethingElse(function () {
            ...
            console.log('done');
        });
    });
});

//ES6
doSomething()
    .then(doSomethingElse)
    .then(maybeSomethingElse)
    .then(() => console.log('done'))
    .catch((err) => console.log(err));
                
            
                
'use strict';

const doSomethingAsync = (something) => {
    return new Promise((resolve, reject) => {
        doSomething(something, (err, result) => {
            if (err) {
                reject(err);
            } else {
                resolve(result);
            }
        });
    });
};

doSomethingAsync('toto')
    .then((result) => console.log(result))
    .catch((err) => console.log(err));
                
            
                
// ./services/myService.js
'use strict';

export class Say extends Something {
    constructor(msg) {
        this.msg = msg;
    }
    saySomething() {
        console.log(this.msg);
    }
};

export getSomething = () => {
    return fetch('http://my-api.io/poneys');
};

// app.js
'use strict';

import { Say, getSomething } from './services/myService';

const say = new Say('hello world');
say.saySomething(); //hello world

getSomething().then(doSomething);
                
            

λ

Vous prendrez bien un peu de programmation fonctionnelle ?

f : inputoutput

vs

mauvaises pratiques jQuery

λ en JS (extraits)

  • Fonctions pures
  • map, filter, reduce
  • Clôture/Closure
                
'use strict';

const $form = $('.js-my-form'),
      $cgu  = $('.js-cgu');

//BAD
const checkAndSubmit = () => {
    if ($cgu.prop('checked')) {
        $form.submit();
    }
};
checkAndSubmit();

//GOOD
const checkAndSubmit = ($form, $cgu) => {
    if ($cgu.prop('checked')) {
        $form.submit();
    }
};
checkAndSubmit($form, $cgu);

                
            
                
'use strict';

const petitsPoneys = [ ... ];

const grandsPoneys = petitsPoneys.map(grandir);

const grandsPoneysRouges = grandsPoneys.filter(
    (p) => p.color === 'red'
);

const famillePoney = grandsPoneys.reduce(
    (famille, poney) => {
        return `${famille} ${poney.emoji}`;
    },
    'Famille : '
); // 'Famille : 🐴 🐎 🏇

                
            
                
'use strict';

//BTW, don't use the for loop

const poneys = [ ... ];

//BAD
for (const i = 0; i < poneys.length; i++) {
    const poney = poneys[i];
    doSomething(poney);
}

//GOOD
poneys.forEach(doSomething);

                
            
                
'use strict';

const buildColorFilter = (color) =>
    (poney) => {
        return poney.color === color;
    }
;

const
    filterRouge = buildColorFilter('red'),
    filterBleu  = buildColorFilter('blue');

const
    poneysRouges = poneys.filter(filterRouge),
    poneysBleus  = poneys.filter(filterBleu);

                
            

λ en JS

  • Moins de bugs
  • Fonctions puissantes
  • Test facile

(Ça doit faire cet effet là)

2. React

Eh bah enfin !

React

C'est quoi ?

  • Librairie de templating

    ie. ≠ framework, ≠ modèle

  • Caractéristiques

    Composants, JSX, Performance

  • De l'HTML dans du JS

data ⟷ vue HTML

Exemple

React

Un composant

  • Le créer
  • L'attacher
Exemples
                
import React from 'react';
import ReactDOM from 'react-dom';

class Example2_1 extends React.Component {
    render() {
        return (
            <p>Hello world</p>
        );
    }
};

ReactDOM.render(
    <Example2_1 />,
    document.querySelector('.js-react-example2-1')
);
                
            
                
import React from 'react';
import ReactDOM from 'react-dom';

const Example2_2 = () => (
    <p>Hello world</p>
);

ReactDOM.render(
    <Example2_2 />,
    document.querySelector('.js-react-example2-2')
);
                
            

React

props

Passer n'importe quoi au composant

  • Des données
  • Des fonctions
Exemples
                
class Example3_1 extends React.Component {
    render() {
        return (
            <p>{this.props.text}</p>
        );
    }
};

ReactDOM.render(
    <Example3_1
        text="Petit poney"
        />,
    document.querySelector('.js-react-example3-1')
);
                
            
                
const Example3_2 = (props) => (
    <p>{props.text}</p>
);

ReactDOM.render(
    <Example3_2
        text="Petit poney"
        />,
    document.querySelector('.js-react-example3-2')
);

                
            
                
const Example3_2 = (props) => (
    <p
        onClick={props.coucou}>
        {props.text}
        </p>
);

ReactDOM.render(
    <Example3_2
        text="Petit poney"
        coucou={() => alert('coucou');} />,
    document.querySelector('.js-react-example3-2')
);

                
            

React

Jouer aux Legos™

ie. composition de composants

Exemple
                

const Item = (props) => (
    <li>{props.emoji}</li>
);

const List () => (
    <div>
        <p>Famille Poney:</p>
        <ul>
            <Item emoji="🐴" />
            <Item emoji="🐎" />
            <Item emoji="🏇" />
        </ul>
    </div>
);

ReactDOM.render(
    <List />,
    document.querySelector('.js-react-example4-1')
);
                
            
                

const List = (props) => {
    const poneys = props.poneys.map((p, i) => (
        <Item key={i} emoji={p} />
    ));
    return (<div>
        <p>Famille Poney :</p>
        <ul>{poneys}</ul>
    </div>);
};

const poneys = ['🐴', '🐎', '🏇'];

ReactDOM.render(
    <List poneys={poneys} />,
    document.querySelector('.js-react-example4-1')
);
                
            

React

state

L'état du composant

Exemple
                

const Item = ({ poney }) => (
    <li>{poney.emoji} ({poney.color})</li>
);

const List = (props) => {
    var poneys = props.poneys.map((p, i) => (
        <Item key={i} poney={p} />
    ));
    return (<div>
        <p>Famille Poney :</p>
        <ul>{poneys}</ul>
    </div>);
};

const poneys = [{ emoji: '🐴', color: 'red' }, ... ];

ReactDOM.render(
    <List poneys={poneys} />,
    document.querySelector('.js-react-example5-1')
);
                
            
                

const FilterBar = ({ filter }) => (
    <div>
        <button
            onClick={() => filter('all')}>
            Tout
        </button>
        <button
            onClick={() => filter('red')}>
            Rouge
        </button>
        <button
            onClick={() => filter('blue')}>
            Bleu
        </button>
    </div>
);
                
            
                
class List extends React.component {
    constructor() {
        super();
        this.state = {
            filter: 'all',
        };
    }
    _handleFilter(filter) {
        this.setState({ filter });
    }
    ...
                
            
                
    ...
    render() {
        var poneys = this.props.poneys
            .filter((p) => {
                return this.state.filter === 'all'
                    || this.state.filter === p.color;
            })
            .map((p, i) => (
                <Item key={i} poney={p.emoji} />
            ));

        return (<div>
            <p>Famille Poney :</p>
            <ul>{poneys}</ul>
            <FilterBar
                filter={this._handleFilter.bind(this)}
            />
        </div>);
    }
};
                
            

props vs state

props state
Pourquoi ? Configuration État
Présence Habituelle Rare
Modifiable* Non (immuable) Oui**
Utilisation Tout le temps Le moins possible

* Du point de vue du composant

** Crée un nouveau render() du composant

React

☞ Point de mi-parcours

  • système de vue puissant
  • retour au JS

☞ Mais ne résout pas tous les problèmes

Que se passe-t-il si le même jeu de données est utilisé par plusieurs composants ?

3. Redux

Redux

C'est quoi ?

  • Anti-MVC
  • "Juste" une architecture
Redux is a predictable state container for JavaScript apps.
redux.js.org

(C'est vite le bazar)

Data-flow unidirectionnel

Une seule source de vérité :
le state*

reducer: (state, action) ⟼ state

* immuable

Redux

Créer les vues

Exemple
                
/**
 * @param {Object} props.poney
    { id: 1, emoji: '🐴', color: 'red',
        checked: false }
 * @param {Function} props.toggle
 */
const Item = ({ poney, toggle }) => {
    return (<li>
        <input
            type="checkbox"
            defaultChecked={poney.get('checked')}
            onClick={() => toggle(poney)} />
        <label>
            { poney.get('emoji') }
        </label>
    </li>);
};
                
            
                
const List = ({ poneys, togglePoney }) => {
    const items = poneys.map((p, i) => (
        <Item key={i} poney={p}
            toggle={togglePoney} />
    ));
    return (<div>
        <p>Famille Poney :</p>
        <ul>{ items }</ul>
    </div>);
};
                
            
                
const Summary = ({ poneys }) => {
    const n = poneys.reduce((count, p) => (
        p.get('checked') ? count + 1 : count
    ), 0);
    return (<p>
        { n }
        { n > 1
            ? 'poneys sélectionnés'
            : 'poney sélectionné' }
    </p>);
};
                
            
                
const App = (props) => (<div>
    <List {...props} />
    <Summary poneys={props.poneys} />
</div>);
                
            

Redux

Créer les actions

Exemple

Par ici pour des actions asynchrones

                
const togglePoney = (poney) => {
    return {
        type: 'TOGGLE_PONEY',
        poney,
    };
};
                
            

Redux

Créer un reducer

Exemple

Reducer: (state, action) ⟼ state

(state, action) ⟼ new state*

* Immutable.js

                
import { Map } from 'immutable';

/**
 * @param  {Object} state
 * @param  {Object} action
 *
 * @return {Object}         New state
 */
const reducer = (state = Map(), action) => {
    switch(action.type) {
        ...
        default:
            return state;
            break;
    }
};
                
            
                
import { Map, fromJS } from 'immutable';

const reducer = (state = Map(), action) => {
    switch(action.type) {
        case 'INIT':
            return fromJS(action.state);
            break;
        default:
            return state;
            break;
    }
};
                
            
                
import { Map, fromJS } from 'immutable';

const reducer = (state = Map(), action) => {
    switch(action.type) {
        ...
        case 'TOGGLE_PONEY':
            const poneyIndex = state
                .get('poneys')
                .findIndex((p) =>
                    p.get('id') === action.poney.get('id')
                );

            return state.setIn(
                ['poneys', poneyIndex, 'checked'],
                !action.poney.checked
            );
            break;
        ...
    }
};
                
            

Redux

Créer le store

Exemple
                
import { createStore } from 'redux';

const store = createStore(reducer);

store.dispatch({
    type:  'INIT',
    state: {
        poneys: [{
            id:      1,
            emoji:   '🐴',
            checked: false
        }, ... ],
    },
});
                
            

2 types de composants

Présentation Container
Pourquoi ? Vue Logique
Redux ? Non Oui
Data props Redux state
                
import { connect, Provider } from 'react-redux';

import App from './components/App.jsx';
import togglePoney from './actions";

const mapStateToProps = (state) => ({
    poneys: state.get('poneys'),
});

const AppContainer = connect(
    mapStateToProps, //map store et props
    { togglePoney }  //map actions et props
)(App);

ReactDOM.render(
    <Provider store={store}>
        <AppContainer />
    </Provider>,
    document.querySelector('.js-redux-example2')
);
                
            

Redux

  • Data-flow unidirectionnel
  • λ
  • Composants

    Présentation vs containers

reducer: (state, action) ⟼ state

4. Outils et Méthode

ie. gérons tout ça comme des pros

Architecture

À garder en tête :

  • Separation of concern*
  • Module ES6

* Talk de @FGRibreau

                
.
├── sass/
├── js/
|   ├── utils/
|   ├── components/
|   |   ├── App.jsx
|   |   ├── Graph.jsx
|   |   ├── Box.jsx
|   |   └── Button.jsx
|   ├── services/
|   |   ├── Stats.js
|   |   └── Auth.js
|   ├── actions/
|   |   ├── graph_actions.js
|   |   └── button_actions.js
|   ├── test/
|   |   ├── Stats.js
|   |   └── App.jsx
|   ├── reducer.js
|   └── index.js
└── index.html
                
            
                
.
├── sass/
├── js/
|   ├── stats/
|   |   ├── components/
|   |   ├── services/
|   |   ├── actions/
|   |   ├── test/
|   |   ├── reducer.js
|   |   └── index.js
|   ├── catalog/
|   |   ├── components/
|   |   ├── services/
|   |   ├── actions/
|   |   ├── test/
|   |   ├── reducer.js
|   |   └── index.js
└── index.html
                
            

Lint

Inspectons tout ça !

Moins de bugs, dev plus facile, consitance du code, etc. 👍

                
// .eslintrc.json
{
    "parserOptions": {
        "ecmaVersion": 6,
        "sourceType": "module",
        "ecmaFeatures": {
            "jsx": true
        }
    },
    "extends": "airbnb",
    "rules": {
        "semi":   ["error", "always"],
        "quotes": ["error", "simple"],
        ...
    },
}
                
            

(ESLint branché sur un gulp watch)

Tests

  • Mocha
  • Chai
  • Enzyme

TDD 🤓

                
import reducer from '../reducer';

describe('reducer', () => {

    it('handles INIT', () => {
        ...
    });

    it('handles TOGGLE_PONEY', () => {
        ...
    });

});
                
            
                
import { expect } from 'chai';
import Api from '../services/api';

describe('API service', () => {

    //if synchronous
    it('returns 2 with returns2()', () => {
        expect(Api.returns2()).to.equal(2);
    });

});
                
            
                
import { expect } from 'chai';
import Api from '../services/api';

//mock with fetch-mock here

const poneys = [{ emoji: '🐴', color: 'red' }, ... ];

describe('API service', () => {

    it(
        'returns poneys with getPoneys()',
        function (done) {
            Api.getPoneys()
                .then((result) => {
                    expect(result).to.deep.equal(poneys)
                    done();
                })
                .catch(done);
        }
    );

});
                
            
                
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';

import Item from '../components/Item';

describe('Item', () => {

    it('renders an item', () => {
        const TEXT = 'Poney';
        const wrapper = shallow(
            <Item text={TEXT} />
        );

        expect(wrapper.find('li')).to.have.length(1);
        expect(wrapper.find('label').text()).to.equal(TEXT);
    });

});
                
            
                
import React from 'react';
import { expect } from 'chai';
import { shallow, render } from 'enzyme';

import Item from '../components/Item';

describe('Item', () => {

    it(
        'invokes callback when the poney is clicked',
        function (done) {
            const PONEY = { ... };
            const checkCb = (poney) => {
                expect(poney).to.equal(PONEY)
                done();
            };

            const wrapper = shallow(
                <Item poney={PONEY} toggleItem={checkCb}/>
            );

            wrapper.find('input').simulate('click');
        }
    );

});
                
            

mocha

Build

Webpack

  • Modules
  • Dépendances
  • ES6 → préhistoire
  • Compression
  • Dev tools

Outils et Méthode

  • Architecture
  • Lint
  • Tests
  • Build

À intégrer dans grunt/gulp et c'est parti ! 🚀

De quoi a-t-on parlé ?

  • 1. JS like a boss
    • Revenir au JS vanilla
  • 2. React
    • De bien jolis composants
  • 3. Redux
    • Ajouter un peu d'intelligence
  • 4. Outils et Méthode
    • Let tools help you

Conclusion

🎉 Le framework JS made in Lengow 🎉

  • Vues : React
  • Modèles* : Redux + libs
  • Services : Vanilla + libs
  • Archi : Fait maison

* On se comprend, hein

Démo

Merci !