* légèrement édité
Dis moi c'est où t'as mal ?
Ah oui
The jQuery Effect™
Grooos fichiers
//bad if ($('.toto').hasClass('is-active')) { $('.titi').hide(); } else { $('titi').show(); }
ES6, Webpack
Angular, Ember, Redux*, etc.
* C'est pas un framework mais c'est pas grave
React, Vue.js, etc.
Mocha, chai
ES6, λ
Le système de vue
reducer: (state, action) ⟼ state
Webpack, ESLint, Tests
Quelques petits trucs sympas* pour écrire du meilleur code.
* Parmi pleins d'autres
'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 : input ⟼output
vs
mauvaises pratiques jQuery
'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);
(Ça doit faire cet effet là)
Eh bah enfin !
ie. ≠ framework, ≠ modèle
Composants, JSX, Performance
data ⟷ vue HTML
Exemple
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') );
Passer n'importe quoi au composant
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') );
ie. composition de composants
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') );
L'état du composant
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>); } };
* Du point de vue du composant
** Crée un nouveau render() du composant
☞ Point de mi-parcours
☞ 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 ?(C'est vite le bazar)
Data-flow unidirectionnel
Une seule source de vérité : le state*
reducer: (state, action) ⟼ state
* immuable
/** * @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>);
const togglePoney = (poney) => { return { type: 'TOGGLE_PONEY', poney, }; };
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; ... } };
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 stateimport { 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') );
Présentation vs containers
reducer: (state, action) ⟼ state
ie. gérons tout ça comme des pros
. ├── 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
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)
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
Webpack
À intégrer dans grunt/gulp et c'est parti ! 🚀
🎉 Le framework JS made in Lengow 🎉
* On se comprend, hein
Un peu de lecture