Allez, tant pis, on saute l'intro et on entre directement dans le vif du sujet (on n'a pas que ça à foutre, après tout).
Il était une fois un gros site web que s'apelorio Facebook. Qui dit Facebook, dit webapp plus grosse que la plus grosse de tes copines (no offense 1) ; et du coup, propension à se retrouver submergé de bugs plus élevée.
Les ingénieurs front-end de Facebook, confrontés à une codebase de plus en plus bordélique (personne ne voulant toucher certaines parties de celle-ci) ont dû repenser la structure des composants les plus cruciaux.
Face à ce besoin, ces développeurs sont donc parvenus à deux solutions :
Flux n'est pas un framework, mais simplement une architecture, une sorte de guideline qui résout pas mal de problèmes ayant pu apparaître avec les divers bibliothèques et frameworks MV* apparus lors des dernières années.
Flux comporte 4 concepts :
EventEmitter
global ;model
de l'architecture MVC, ils
contiennent les données, et réagissent aux actions que le dispatcher leur
transmet ;Jusque-là, rien de bien fou. C'est dans leur manière d'interagir que la particularité se dessine :
En effet, le flux en question est unidirectionnel. Pour faire simple, on procède ainsi :
On définit une action via un action creator (on passera toujours par ces action-creators pour signaler une action) :
// actions/BasketActions.js
var AppDispatcher = require("../AppDispacher");
/*
On garde un dictionnaire des types d'actions
afin d'avoir un fichier donnant une vision
globale de toutes les interactions de l'app.
*/
var ActionTypes = require("../constants").ActionTypes;
module.exports = {
addToBasket: function(productId) {
AppDispatcher.handleViewAction({
type: ActionTypes.ADD_TO_BASKET,
productId: productId,
});
},
};
On lancera par la suite cette action lorsque l'utilisateur aura cliqué sur un certain bouton, dans la vue concernée.
// components/Product.jsx
var React = require("react/addons");
var BasketActions = require("../actions/BasketActions");
var Button = require("./common/Button.jsx");
var Product = React.createClass({
addToBasket: function(productId) {
BasketActions.addToBasket(productId);
},
render: function() {
return (
<div className="Product">
{/* rest of the component*/}
<Button
onClick={this.addToBasket.bind(this.props.productId)}
label="Add to basket"
/>
</div>
);
},
});
module.exports = Product;
Dès lors, à chaque clic sur le bouton en question, l'action ADD_TO_BASKET
sera
passée au dispatcher, qui le signalera aux stores.
// stores/BasketStore.js
var AppDispatcher = require("../AppDispatcher");
var ActionTypes = require("../constants").ActionTypes;
var API = require("../api");
var merge = require("../utils/merge");
var Store = require("../utils/store");
var _store = {
products: [],
};
var BasketStore = merge(Store, {
/*
Ici, on `register` un callback sur l'AppDispatcher,
ce qui signifie qu'on verra passer toutes les actions
de l'app.
*/
dispatchToken: AppDispatcher.register(function(payload) {
var action = payload.action;
switch (action.type) {
case ActionTypes.ADD_TO_BASKET:
/*
L'API va ajouter le produit et lancer une
action `BASKET_UPDATED` dès que le serveur a répondu.
*/
API.addToBasket(action.productId);
break;
case ActionTypes.BASKET_UPDATED:
/*
L'API a répondu, on peut stocker la réponse
et signaler le changement
aux vues récupérant ces données.
*/
_store = action.reponse;
BasketStore.emitChange();
break;
default:
break;
}
}),
});
module.exports = BasketStore;
La vue, quant à elle, sera notifiée du changement, et effectuera un render()
:
// components/Basket.jsx
var React = require("react/addons");
var BasketActions = require("../actions/BasketActions");
var BasketStore = require("../stores/BasketStore");
function getState() {
return BasketStore.getStore();
}
var Product = React.createClass({
getInitialState: function() {
return getState();
},
_onStoreChange: function() {
/*
À chaque changement du store, on update naïvement
le component et on laisse le virtual DOM faire son job.
*/
this.setState(getState());
},
componentDidMount: function() {
/*
On écoute le store uniquement lorsque le
component est monté.
*/
BasketStore.addChangeListener(this._onStoreChange);
},
componentWillUnmount: function() {
/*
Et on arrête d'écouter quand il ne l'est plus.
*/
BasketStore.removeChangeListener(this._onStoreChange);
},
render: function() {
return (
<div className="Basket">
<div className="Basket-count">
{this.state.products.length + " products"}
</div>
{/* rest of the component */}
</div>
);
},
});
module.exports = Product;
Tout cela peut sembler relativement verbeux, mais il faut préciser deux choses :
StoreMixin
simplifier la déclaration des
class
React ;Si React et Flux vont si bien ensemble, c'est que l'approche de rendu "naïf" de
React (comprendre "React s'en fout de ce qui change, il appelle render
à
chaque changement") permet de réduire la logique à écrire dans les Stores, et
donc de simplifier très fortement la codebase de l'app.
Lorsque qu'un ou plusieurs stores composent l'état d'un state React, alors à
chaque changement de l'un de ces stores, tous les composants React concernés et
leurs enfants vont appeler leur méthode render()
. Afin d'éviter des appels
superflus à ces méthodes, React donne la possibilité de tester soi-même s'il est
nécessaire de mettre à jour le component en déclarant une méthode
shouldComponentUpdate
retournant un boolean
qui stipulera si oui ou non il
est nécessaire d'appeler render()
.
Bisous bisous.
Pour aller un peu plus loin :