React & Redux : Les concepts

React et Redux pour vos applications

React est devenu une référence dans le monde des développeurs front-end. Il a même su s’imposer au sein des back-end grâce à NodeJS et aux systèmes d’isomorphisme. React se positionne principalement en concurrent direct d’Angular et de Vue bien que ce dernier soit arrivé plus tard et ce soit fortement inspiré de React. Angular se présente comme un véritable framework avec tous ses outils ainsi que d’une arborescence définie. Du côté de React, il est difficile d’en dire autant. Ce n’est pas un framework, il s’agit d’un outils et par conséquent il est beaucoup plus flexible. Il sera plus adapté pour implémenter des briques isolés de votre page. React est un formidable outils, d’autant plus qu’avec la surcouche JSX, il devient très agréable de développer avec. L’association avec Redux permet de décupler les possibilités. Petit tour d’horizon !

React

React est un système permettant de faire de le rendu de composants. Un peu à la manière d’un DOM, des composants contiennent d’autres composants. La mise à jour d’un composant entraîne la mise à jour des composants enfants en cascade. React utilise le VirtualDOM ce qui représente l’un de ses plus forts atouts en terme de performance.

JSX

React peut s’écrire avec la “véritable syntaxe” JavaScript mais cela est moins facile à écrire et encore moins à lire. JSX apporte un véritable souplesse dans l’écriture, dans la lecture et la compréhension. Babel propose un preset pour JSX ce qui permet de transpiler le JSX en code JavaScript classique et exécutable par le navigateur.

Sans JSX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Hello extends React.Component {
render() {
return React.createElement(
'div',
null,
React.createElement(
'h1',
null,
'Title'
),
React.createElement(
'p',
null,
'Description'
)
);
}
}

Avec JSX

1
2
3
4
5
6
7
8
9
10
class Hello extends React.Component {
render() {
return <div>
<h1>Title</h1>
<p>
Description
</p>
</div>
}
}

ES2015

Autant se le dire maintenant, React utilise tous les avantages et nouveautés de ES2015 (anciennement appelé ES6). Sans ES2015, vous devrez écrire un code bien moins buvable. Il est fortement conseillé d’utilisé ES2015 et de le transpiler grâce à Babel.

ECMAScript 5

1
2
3
4
5
6
7
8
var Hello = React.createClass({
getInitialState: function() {
return {};
},
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});

ECMAScript 2015 (ES6)

1
2
3
4
5
6
7
8
9
10
11
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

CommonJS vs. Module ES2015

Pour utiliser ES2015, il vous sera fortement conseillé d’utiliser la norme CommonJS ou les modules ES2015. Pour rappel, CommonJS est la nomenclature utilisée par Node.js. Cela permet de facilement découper son code et d’utiliser des composants externes. Cela n’est pas une obligation même si ignorer leur intérêt vous fera perdre de nombreuses heures. Du côté des outils, Browserify compile du CommonJS (la norme actuellement). webpack lui compile les deux.

Syntaxe CommonJS

1
const React = require("react");

Syntaxe ES2015

1
import {Component} from react;

Personnellement, j’aimerai utiliser le standard défini (soit les modules ES2015) mais CommonJS s’est fortement imposé grâce à Node.js. Dans certains cas, un module peut être fonctionnel pour Node.js (back-end) ainsi que dans le browser (front-end). De plus, même si dans la majorité des cas, un module peut être fonctionnel avec CommonJS et ES2015, il arrive parfois qu’on rencontre des problèmes d’import avec ES2015.

Exemple d’utilisation avec 2 composants

Composant List.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const React = require("react"),
Item = require("./item.jsx");
class List extends React.Component {
render() {
return <ul>
{this.props.items.map((item) => <Item
content={item.content}
/>)}
</ul>;
}
}
module.exports = List;

Composant Item.jsx

1
2
3
4
5
6
7
8
9
10
11
const React = require("react");
class Item extends React.Component {
render() {
return <li>
{this.props.content}
</li>;
}
}
module.exports = Item;

Génération de l’UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const React = require("react"),
ReactDOM = require("react-dom"),
List = require("./list.jsx"),
items = [{
content: "Foo"
}, {
content: "Bar"
}, {
content: "Baz"
}];
ReactDOM.render(
<List
items={items}
/>,
document.getElementById("container")
);

Redux

Redux se présente comme un système de centralisation des données et des actions. React est particulièrement convaincant lorsqu’on commence mais on se retrouve rapidement confronter à certains problèmes de conceptions lorsque nos applications deviennent un peu plus élaborer qu’une simple liste. Par exemple, admettons que nous ayons divers composants au sein de notre page mais qu’ils doivent partager certaines propriétés, nous nous retrouvons rapidement dans l’impasse. Redux propose une solution qui n’est pas toujours facile à comprendre sur le site.

Principe

Dans React, on distingue deux types de données :

  • Les propriétés (props) qui sont accessibles uniquement en lecture seule
  • L’état (state) qui est disponible en lecture/écriture mais qui reste propre à un composant (local).

La modification du state local ou la réception de nouvelles propriétés (mise à jour du composant) entraîne un rafraîchissement de l’UI.

Avec Redux, on ajoute la notion de store. Un store est global. Il contient toutes les informations de tous les composants qui y sont rattachés. Lorsqu’un composant A lance une action et entraîne une modification du store (dit aussi “state global”), le composant B aura également les nouvelles données du composant A. Chaque composant peut mettre en place des comportements différents en fonction de l’état des autres.

Le store va également contenir les actions disponibles dans les composants.

Mise en place

Techniquement, il est possible de mettre en place React et Redux de manière manuelle mais il existe une autre méthode qui permet de découper son code plus proprement et de profiter de certains mécanismes automatique.

Exemple de mise en place

Arborescence

main.js sera votre fichier d’entrée. components contiendra tous vos composants. Le dossier redux contiendra deux fichiers pour centraliser la partie métier de votre application.

Ce découpage doit bien évidemment être affiné en fonction de la taille de votre application.

1
2
3
4
5
6
7
8
./
|_ main.js
|_ components
|_ list.jsx
|_ item.jsx
|_ redux
|_ actions.js
|_ reducers.js

Entrypoint

Dans votre fichier main.js nous allons monter notre composant List et instancier le provider Redux de manière à interfacer notre composant List et un store Redux.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const List = require("./components/list.jsx"),
Provider = require("react-redux").Provider,
React = require("react"),
ReactDOM = require("react-dom"),
reducers = require("./redux/reducers.js"),
Redux = require("redux");
let store = null;
module.exports = () => {
store = Redux.createStore(
reducers,
{
items: []
}
);
ReactDOM.render(
<Provider store={store} >
<List/>
</Provider>,
document.getElementById("container")
);
};

Composants

Composant List

Le composant List possède le même comportement mais au lieu d’exporter le composant, on exporte une instance de react-redux qui injectera le state global (propriétés du store Redux) dans le composant List.

deleteItem est une action qui est déclarée dans le fichier actions.js. Cette action sera disponible dans les propriétés.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const actions = require("./redux/actions.js"),
Item = require("./item.jsx"),
React = require("react"),
ReactRedux = require("react-redux");
class List extends React.Component {
render() {
return <ul>
{this.props.items.map((item) => <Item
content={item.content}
deleteItem={this.props.deleteItem.bind(null, item)}
/>)}
</ul>;
}
}
module.exports = ReactRedux.connect(
(state = {}) => state,
(dispatch, props) => Object.assign({}, props, {
deleteItem: actions.deleteItem.bind(null, dispatch)
})
)(List);
Composant Item

Le composant Item ne change pas réellement. Voici simplement le composant avec l’utilisation de l’action deleteItem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const React = require("react");
class Item extends React.Component {
render() {
return <li>
{this.props.content}
<a onClick={this.props.deleteItem}>
Supprimer
</a>
</li>;
}
}
module.exports = Item;

Gestion des actions

Les actions vont être stockées dans le fichier redux/actions.js.

1
2
3
4
5
6
7
8
9
10
function deleteItem(dispatch, item) {
dispatch({
type: "REMOVE_ITEM",
item: item
});
}
module.exports = {
deleteItem: deleteItem
};

Le reducers

Le reducers vous permet de gérer la mise à jour des données. Le reducers et les actions sont complémentaires. Les traitements comme les requêtes sont réalisées dans le fichier actions.js et le reducers s’occupent de mettre à jour les données du store (state global).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function reducer(state, action) {
const newState = Object.assign({}, state);
switch (action.type) {
case "REMOVE_ITEM":
const index = newState.items.indexOf(action.item);
if (index !== -1) {
newState.items.splice(index, 1);
}
break;
default:
return state;
}
return newState;
}
module.exports = reducer;

Conclusion

React est un outils puissant mais qui manque rapidement de fonctionnalités de gestion lorsqu’on travaille sur des comportements plus avancées qui impliquent plusieurs composants distants. Redux propose un système intéressant et normalisant. L’utilisation du package react-redux et du Redux provider simplifie grandement l’implémentation des interfaces.