Naviguer dans l'écosystème React.js

Publié: 2022-03-11

naviguer dans l'écosystème de react.js

La vitesse d'innovation dans JavaScript Land est si élevée que certaines personnes pensent même que c'est contre-productif. Une bibliothèque peut passer de l'adoption précoce du jouet à l'état de l'art, puis à l'obsolescence en quelques mois. Être capable d'identifier un outil qui va rester pertinent pendant au moins une autre année devient un art en soi.

Lorsque React.js a été publié il y a deux ans, je venais juste d'apprendre Angular, et j'ai rapidement rejeté React comme une obscure encore une autre bibliothèque de modèles. Au cours de ces deux années, Angular a vraiment pris pied parmi les développeurs JavaScript et est presque devenu synonyme de développement JS moderne. J'ai même commencé à voir Angular dans des environnements d'entreprise très conservateurs, et j'ai pris son brillant avenir pour acquis.

Mais soudain, une chose étrange s'est produite. Il semble qu'Angular ait été victime de l'effet Osborne, ou "mort par annonce préalable". L'équipe a annoncé que, premièrement, Angular 2 sera complètement différent, sans chemin de migration clair depuis Angular 1, et, deuxièmement, qu'Angular 2 ne sera pas disponible avant environ un an. Qu'est-ce que cela dit à quelqu'un qui veut démarrer un nouveau projet Web ? Vous souhaitez écrire votre nouveau projet dans un framework qui va être rendu obsolète par une nouvelle version ?

Cette angoisse des développeurs a fait le jeu de React, qui cherchait à s'imposer dans la communauté. Mais React s'est toujours présenté comme le "V" de "MVC". Cela a causé une certaine frustration chez les développeurs Web, habitués à travailler avec des frameworks complets. Comment remplir les pièces manquantes ? Dois-je écrire le mien ? Dois-je simplement utiliser une bibliothèque existante ? Si oui, lequel ?

Effectivement, Facebook (créateurs de React.js) avait un atout de plus dans le trou : le flux de travail Flux, qui promettait de remplir les fonctions « M » et « C » manquantes. Pour rendre les choses encore plus intéressantes, Facebook a déclaré que Flux est un "modèle", pas un cadre, et leur implémentation de Flux n'est qu'un exemple du modèle. Fidèles à leur parole, leur implémentation était vraiment simpliste et impliquait d'écrire beaucoup de passe-partout verbeux et répétitif pour faire avancer les choses.

La communauté open source est venue à la rescousse, et un an plus tard, nous avons des dizaines de bibliothèques Flux, et même quelques méta-projets visant à les comparer. C'est une bonne chose; au lieu de publier un cadre d'entreprise prêt à l'emploi, Facebook a réussi à susciter l'intérêt de la communauté et a encouragé les gens à proposer leurs propres solutions.

Il y a un effet secondaire intéressant à cette approche : lorsque vous devez combiner un grand nombre de bibliothèques différentes pour obtenir votre framework complet, vous échappez efficacement au verrouillage du fournisseur, et les innovations qui émergent lors de la construction de votre propre framework peuvent être facilement réutilisées. ailleurs.

C'est pourquoi les nouveautés autour de React sont si intéressantes ; la plupart d'entre eux peuvent être facilement réutilisés dans d'autres environnements JavaScript. Même si vous ne prévoyez pas d'utiliser React, jeter un œil à son écosystème est inspirant. Vous souhaiterez peut-être simplifier votre système de construction à l'aide du puissant, mais relativement facile à configurer, module bundler Webpack, ou commencer à écrire ECMAScript 6 et même ECMAScript 7 dès aujourd'hui avec le compilateur Babel.

Dans cet article, je vais passer en revue certaines des fonctionnalités et bibliothèques intéressantes disponibles. Alors, explorons l'écosystème React !

Réagissez l'homme explorateur

Système de construction

Le système de construction est sans doute la première chose dont vous devriez vous soucier lors de la création d'une nouvelle application Web. Le système de construction n'est pas seulement un outil pour exécuter des scripts, mais dans le monde JavaScript, il façonne généralement la structure générale de votre application. Les tâches les plus importantes qu'un système de build doit couvrir sont les suivantes :

  • Gestion des dépendances externes et internes
  • Exécution de compilateurs et de préprocesseurs
  • Optimisation des actifs pour la production
  • Exécution du serveur Web de développement, de l'observateur de fichiers et du chargeur de navigateur

Ces dernières années, le flux de travail Yeoman avec Bower et Grunt a été présenté comme la sainte trinité du développement frontal moderne, résolvant respectivement les problèmes de génération standard, de gestion des packages et d'exécution des tâches courantes, les personnes les plus progressistes passant de Grunt à Gulp récemment.

Dans l'environnement React, vous pouvez les oublier en toute sécurité. Non pas que vous ne puissiez pas les utiliser, mais il y a de fortes chances que vous puissiez vous contenter d'utiliser Webpack et le bon vieux NPM. Comment est-ce possible? Webpack est un bundler de modules, qui implémente la syntaxe de module CommonJS, courante dans le monde Node.js, également dans le navigateur. Cela simplifie en fait les choses puisque vous n'avez pas besoin d'apprendre un autre gestionnaire de packages pour le front-end; vous utilisez simplement NPM et partagez les dépendances entre le serveur et le frontal. Vous n'avez pas non plus besoin de gérer le problème du chargement des fichiers JS dans le bon ordre, car il est déduit des importations de dépendances spécifiées dans chaque fichier, et l'ensemble de l'essaim est correctement concaténé à un script chargeable.

Logo Webpack
Webpack

Pour rendre les choses encore plus attrayantes, Webpack, contrairement à son ancien cousin Browserify, peut également gérer d'autres types d'actifs. Par exemple, avec les chargeurs, vous pouvez transformer n'importe quel fichier d'actif en une fonction JavaScript qui intègre ou charge le fichier référencé. Alors, oubliez le prétraitement manuel et le référencement des ressources à partir de HTML. require simplement vos fichiers CSS/SASS/LESS de JavaScript, et Webpack s'occupe du reste avec un simple fichier de configuration. Webpack comprend également un serveur Web de développement et un observateur de fichiers. De plus, vous pouvez utiliser la clé "scripts" dans package.json pour définir les one-liners du shell :

 { "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }

Et c'est à peu près tout ce dont vous avez besoin pour remplacer Gulp et Bower. Bien sûr, vous pouvez toujours utiliser Yeoman pour générer un passe-partout d'application. Ne vous découragez pas lorsqu'il n'y a pas de générateur Yeoman pour ce que vous voulez (les bibliothèques les plus avancées n'en ont souvent pas). Vous pouvez toujours cloner un passe-partout de GitHub et pirater.

En relation: Comment les composants React facilitent les tests d'interface utilisateur

ECMAScript de demain, aujourd'hui

Le rythme de développement du langage JavaScript a considérablement augmenté ces dernières années, et après une période de suppression des bizarreries et de stabilisation du langage, nous voyons maintenant de nouvelles fonctionnalités puissantes arriver. Le projet de spécification ECMAScript 6 (ES6) a été finalisé, et même s'il n'a pas encore été officialisé, il est déjà largement adopté. Les travaux sur ECMAScript 7 (ES7) sont en cours, mais nombre de ses fonctionnalités sont déjà adoptées par les bibliothèques les plus avancées.

Logo ECMAScript 7
ECMAScript 7

Comment est-ce possible? Vous pensez peut-être que vous ne pourrez pas tirer parti de ces nouvelles fonctionnalités JavaScript brillantes tant qu'elles ne seront pas prises en charge dans Internet Explorer, mais détrompez-vous. Les transpileurs ES sont déjà devenus si omniprésents que nous pouvons même nous passer d'un support de navigateur approprié. Le meilleur transpilateur ES disponible actuellement est Babel : il prendra votre nouveau code ES6+ et le transformera en ES5 vanille, vous pouvez donc utiliser n'importe quelle nouvelle fonctionnalité ES dès qu'elle est inventée (et implémentée dans Babel, ce qui se produit généralement assez rapidement).

Logo Babel
Babel

Les dernières fonctionnalités JavaScript sont utiles dans tous les frameworks frontaux, et React a récemment été mis à jour pour fonctionner correctement avec les spécifications ES6 et ES7. Ces nouvelles fonctionnalités devraient éliminer beaucoup de maux de tête lors du développement avec React. Jetons un coup d'œil à certains des ajouts les plus utiles et à la manière dont ils peuvent bénéficier à un projet React. Plus tard, nous verrons comment utiliser certains outils et bibliothèques utiles avec React tout en exploitant cette syntaxe améliorée.

Cours ES6

La programmation orientée objet est un paradigme puissant et largement adopté, mais la vision de JavaScript est un peu exotique. La plupart des frameworks front-end, que ce soit Backbone, Ember, Angular ou React, ont ainsi adopté leurs propres méthodes propriétaires de définition de classes et de création d'objets. Mais avec ES6, nous avons maintenant des classes traditionnelles en JavaScript, et il est tout simplement logique de les utiliser au lieu d'écrire notre propre implémentation. Ainsi, au lieu de :

 React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })

nous pouvons écrire:

 class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }

Pour un exemple plus élaboré, considérez ce code, en utilisant l'ancienne syntaxe :

 React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });

Et comparez à la version ES6 :

 class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0}; constructor(props) { super(props); this.state = {count: props.initialCount}; } state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }

Ici, les méthodes de cycle de vie React getDefaultProps et getInitialState ne sont plus nécessaires. getDefaultProps devient la variable de classe statique defaultProps et l'état initial est simplement défini dans le constructeur. Le seul inconvénient est que les méthodes ne sont plus liées automatiquement, vous devez donc utiliser bind lors de l'appel de gestionnaires depuis JSX.

Décorateurs

Les décorateurs sont une fonctionnalité utile de ES7. Ils vous permettent d'augmenter le comportement d'une fonction ou d'une classe en l'enveloppant dans une autre fonction. Par exemple, supposons que vous souhaitiez avoir le même gestionnaire de modifications sur certains de vos composants, mais que vous ne souhaitiez pas vous engager dans l'antimodèle d'héritage. Vous pouvez utiliser un décorateur de classe à la place. Définissons le décorateur comme suit :

 addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }

L'important ici est que la fonction addChangeHandler ajoute la fonction changeHandler au prototype de la classe cible.

Pour appliquer le décorateur, on pourrait écrire :

 MyClass = changeHandler(MyClass)

ou plus élégamment, avec la syntaxe ES7 :

 @addChangeHandler class MyClass { ... }

En ce qui concerne le contenu de la fonction changeHandler elle-même, avec l'absence de liaison de données bidirectionnelle de React, travailler avec des entrées dans React peut être fastidieux. La fonction changeHandler essaie de le rendre plus facile. Le premier paramètre spécifie une key sur l'objet d'état, qui servira d'objet de données pour l'entrée. Le deuxième paramètre est l'attribut dans lequel la valeur du champ de saisie sera enregistrée. Ces deux paramètres sont définis à partir de JSX à l'aide du mot-clé bind .

 @addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }

Lorsque l'utilisateur modifie le champ de nom d'utilisateur, sa valeur est enregistrée dans this.state.login.username , sans qu'il soit nécessaire de définir d'autres gestionnaires personnalisés.

Fonctions fléchées

Le contexte this dynamique de JavaScript a été une douleur constante pour les développeurs car, de manière peu intuitive, le contexte this d'une fonction imbriquée est réinitialisé à global, même à l'intérieur d'une classe. Pour résoudre ce problème, il est généralement nécessaire de this enregistrer dans une variable de portée externe (généralement _this ) et de l'utiliser dans des fonctions internes :

 class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }

Avec la nouvelle syntaxe ES6, la function(x){ peut être réécrite comme (x) => { . Cette définition de méthode "flèche" non seulement lie correctement this à la portée externe, mais est également considérablement plus courte, ce qui compte certainement lors de l'écriture de beaucoup de code asynchrone.

 onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }

Missions de déstructuration

Les affectations de déstructuration, introduites dans ES6, vous permettent d'avoir un objet composé sur le côté gauche d'une affectation :

 var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true

C'est bien, mais comment cela nous aide-t-il réellement dans React ? Considérez l'exemple suivant :

 function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }

Avec la déstructuration, vous pouvez économiser quelques frappes. Le littéral de clés {url, method, params} reçoit automatiquement des valeurs de portée avec les mêmes noms que les clés. Cet idiome est utilisé assez souvent et l'élimination des répétitions rend le code moins sujet aux erreurs.

 function makeRequest(url, method, params) { var config = {url, method, params}; ... }

La déstructuration peut également vous aider à charger uniquement un sous-ensemble d'un module :

 const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }

Arguments : par défaut, repos et propagation

Les arguments de fonction sont plus puissants dans ES6. Enfin, vous pouvez définir l'argument par défaut :

 function http(endpoint, method='GET') { console.log(method) ... } http('/api') // GET

Fatigué de jouer avec l'objet d' arguments peu maniable ? Avec la nouvelle spécification, vous pouvez obtenir le reste des arguments sous forme de tableau :

 function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }

Et si vous n'aimez pas appeler apply() , vous pouvez simplement répartir un tableau en arguments de fonction :

 myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);

Générateurs et fonctions asynchrones

ES6 a introduit des générateurs JavaScript. Un générateur est essentiellement une fonction JavaScript dont l'exécution peut être interrompue puis reprise plus tard, en se souvenant de son état. Chaque fois que le mot clé yield est rencontré, l'exécution est interrompue et l'argument yield est renvoyé à l'objet appelant :

 function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }

Voici un exemple de ce générateur en action :

 > var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }

Lorsque nous appelons la fonction du générateur, elle s'exécute jusqu'au premier yield , puis s'arrête. Après avoir appelé next() , il renvoie la première valeur et reprend l'exécution. Chaque yield renvoie une autre valeur, mais après le troisième appel, la fonction génératrice se termine et chaque appel suivant à next() renverra { value: undefined, done: true } .

Bien sûr, le but des générateurs n'est pas de créer des séquences numériques élaborées. La partie passionnante est leur capacité à arrêter et reprendre l'exécution de la fonction, qui peut être utilisée pour contrôler le flux de programme asynchrone et enfin se débarrasser de ces fonctions de rappel embêtantes.

Pour démontrer cette idée, nous avons d'abord besoin d'une fonction asynchrone. Habituellement, nous aurions des opérations d'E/S, mais pour plus de simplicité, utilisons simplement setTimeout et renvoyons une promesse. (Notez qu'ES6 a également introduit des promesses natives pour JavaScript.)

 function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }

Ensuite, nous avons besoin d'une fonction consommateur :

 function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }

Cette fonction prend n'importe quel générateur comme argument et continue d'appeler next() tant qu'il y a des valeurs à yield . Dans ce cas, les valeurs fournies sont des promesses, il est donc nécessaire d'attendre que les promesses se résolvent et d'utiliser la récursivité avec loop() pour réaliser une boucle sur les fonctions imbriquées.

La valeur de retour est résolue dans le gestionnaire then() et transmise à value , qui est définie dans la portée externe et qui sera transmise à l'appel next(value) . Cet appel transforme la valeur en résultat de l'expression de rendement correspondante. Cela signifie que nous sommes maintenant capables d'écrire de manière asynchrone sans aucun rappel :

 function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(`Double 1 = ${data1}`); const data2 = yield asyncDouble(2); console.log(`Double 2 = ${data2}`); const data3 = yield asyncDouble(3); console.log(`Double 3 = ${data3}`); } consumer(myGenerator);

Le générateur myGenerator sera mis en pause à chaque yield , en attendant que le consommateur livre la promesse résolue. Et en effet, nous verrons les nombres calculés apparaître dans la console à intervalles d'une seconde.

 Double 1 = 2 Double 2 = 4 Double 3 = 6

Cela démontre le concept de base, cependant, je ne vous recommande pas d'utiliser ce code en production. Au lieu de cela, choisissez une bibliothèque bien testée telle que co. Cela vous permettra d'écrire facilement du code asynchrone avec des rendements, y compris la gestion des erreurs :

 co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });

Ainsi, cet exemple montre comment écrire du code asynchrone sans rappels à l'aide de générateurs ES6. ES7 va encore plus loin dans cette approche en introduisant les mots-clés async et await , et en supprimant complètement le besoin d'une bibliothèque de générateurs. Avec cette fonctionnalité, l'exemple précédent ressemblerait à ceci :

 async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };

À mon avis, cela simplifie le travail avec du code asynchrone en JavaScript. Pas seulement dans React, mais partout ailleurs également.

Les générateurs sont non seulement plus concis et simples, mais nous permettent également d'utiliser des techniques qui seraient très difficiles à mettre en œuvre avec des rappels. Un exemple frappant de bonté du générateur est la bibliothèque middleware koa pour Node.js. Il vise à remplacer Express et, dans ce but, il est doté d'une fonctionnalité qui tue : la chaîne middleware circule non seulement en aval (avec la demande du client), mais également en amont , permettant une modification supplémentaire de la réponse du serveur. Prenons l'exemple de serveur koa suivant :

 // Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); // Response handler app.use(function *(){ this.body = 'Hello'; }); app.listen(3000); 

Logo Koa
Koa

Le middleware de réponse yield s en aval dans le gestionnaire de réponse, qui définit le corps de la réponse, et dans le flux en amont (après l'expression de yield ), une modification supplémentaire de this.body est autorisée, ainsi que d'autres fonctions telles que la journalisation du temps, ce qui est possible car l'amont et l'aval partagent le même contexte de fonction. C'est beaucoup plus puissant qu'Express, dans lequel une tentative d'accomplir la même chose se terminerait comme ceci :

 var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here }); // Response app.use(function (req, res, next){ res.send('Hello World') next(); }); // Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); }); app.listen(3000);

Vous pouvez probablement déjà repérer ce qui ne va pas ici; l'utilisation d'une variable de start "globale" entraînera une condition de concurrence, renvoyant un non-sens avec des requêtes simultanées. La solution est une solution de contournement non évidente, et vous pouvez oublier de modifier la réponse dans le flux en amont.

De plus, lorsque vous utilisez koa, vous obtiendrez gratuitement le flux de travail asynchrone du générateur :

 app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } }); app.listen(3000);

Vous pouvez imaginer les promesses et les rappels impliqués dans la recréation de ce petit exemple dans Express.

Node.js Logo

Quel est le lien entre toutes ces discussions sur Node.js et React ? Eh bien, Node est le premier choix lorsque l'on considère un back-end approprié pour React. Étant donné que Node est également écrit en JavaScript, il prend en charge le partage de code entre le back-end et le front-end, ce qui nous permet de créer des applications Web React isomorphes. Mais, plus à ce sujet plus tard.

Bibliothèque de flux

React est excellent pour créer des composants de vue composables, mais nous avons besoin d'un moyen de gérer les données et l'état dans l'ensemble de l'application. Il a été presque universellement convenu que React est mieux complété par l'architecture d'application Flux. Si vous êtes complètement nouveau sur Flux, je vous recommande un rafraîchissement rapide.

Logo Flux
Flux

Ce qui n'a pas été aussi universellement convenu, c'est laquelle des nombreuses implémentations de Flux choisir. Facebook Flux serait le choix évident, mais pour la plupart des gens, il est trop verbeux. Les implémentations alternatives se concentrent principalement sur la réduction de la quantité de passe-partout requis avec une approche de convention sur la configuration, ainsi que sur certaines fonctions pratiques pour les composants d'ordre supérieur, le rendu côté serveur, etc. Certains des meilleurs prétendants, avec diverses mesures de popularité, peuvent être vus ici. J'ai examiné Alt, Reflux, Flummox, Fluxxor et Marty.js.

Ma façon de choisir la bonne bibliothèque n'est en aucun cas objective, mais cela pourrait quand même aider. Fluxxor était l'une des premières bibliothèques que j'ai consultées, mais maintenant elle semble un peu obsolète. Marty.js est intéressant et a beaucoup de fonctionnalités, mais implique toujours beaucoup de passe-partout, et certaines fonctions semblent superflues. Reflux a fière allure et a une certaine traction, mais semble un peu difficile pour les débutants et manque également de documentation appropriée. Flummox et Alt sont très similaires, mais Alt semble avoir encore moins de passe-partout, un développement très actif, une documentation à jour et une communauté Slack utile. Par conséquent, j'ai choisi Alt.

Flux alternatif

Logo Alt Flux
Flux alternatif

Avec Alt, le flux de travail Flux devient beaucoup plus simple sans rien perdre de sa puissance. La documentation Flux de Facebook en dit long sur le répartiteur, mais nous sommes libres de l'ignorer car, dans Alt, le répartiteur est implicitement lié aux actions par convention et ne nécessite généralement aucun code personnalisé. Cela nous laisse avec juste des magasins , des actions et des composants . Ces trois couches peuvent être utilisées de telle manière qu'elles correspondent parfaitement au modèle de pensée MVC : les magasins sont des modèles , les actions sont des contrôleurs et les composants sont des vues . La principale différence est le flux de données unidirectionnel central du modèle Flux, ce qui signifie que les contrôleurs (actions) ne peuvent pas modifier directement les vues (composants), mais peuvent uniquement déclencher des modifications de modèle (stockage), auxquelles les vues sont passivement liées. C'était déjà une bonne pratique pour certains développeurs Angular éclairés.

Flux de travail Flux vs Alt

Le flux de travail est le suivant :

  1. Les composants initient des actions.
  2. Les magasins écoutent les actions et mettent à jour les données.
  3. Les composants sont liés aux magasins et restitués lorsque les données sont mises à jour.

Actions

Lors de l'utilisation de la bibliothèque Alt Flux, les actions sont généralement de deux types : automatique et manuelle. Les actions automatiques sont créées à l'aide de la fonction generateActions et vont directement au répartiteur. Les méthodes manuelles sont définies comme des méthodes de vos classes d'action, et elles peuvent aller au répartiteur avec une charge utile supplémentaire. Le cas d'utilisation le plus courant des actions automatiques consiste à informer les magasins d'un événement dans l'application. Les actions manuelles sont, entre autres, la manière préférée de gérer les interactions avec le serveur.

Ainsi, les appels d'API REST appartiennent à des actions. Le flux de travail complet est le suivant :

  1. Le composant déclenche une action.
  2. Le créateur de l'action exécute une demande de serveur asynchrone et le résultat est transmis au répartiteur en tant que charge utile.
  3. Le magasin écoute l'action, le gestionnaire d'action correspondant reçoit le résultat en tant qu'argument et le magasin met à jour son état en conséquence.

Pour les requêtes AJAX, nous pouvons utiliser la bibliothèque axios, qui, entre autres, traite les données et les en-têtes JSON de manière transparente. Au lieu de promesses ou de rappels, nous pouvons utiliser le modèle ES7 async / await . Si l'état de la réponse POST n'est pas 2XX, une erreur est générée et nous envoyons soit les données renvoyées, soit l'erreur reçue.

Regardons une page de connexion pour un exemple simple du flux de travail Alt. L'action de déconnexion n'a pas besoin de faire quoi que ce soit de spécial, informez seulement le magasin, afin que nous puissions la générer automatiquement. L'action de connexion est manuelle et attend des données de connexion comme paramètre pour le créateur de l'action. Après avoir reçu une réponse du serveur, soit nous envoyons les données de réussite, soit, si une erreur est générée, nous envoyons l'erreur reçue.

 class LoginActions { constructor() { // Automatic action this.generateActions('logout'); } // Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } } module.exports = (alt.createActions(LoginActions));

Magasins

Le magasin Flux sert à deux fins : il a des gestionnaires d'action et transporte l'état. Continuons notre exemple de page de connexion pour voir comment cela fonctionne.

Créons LoginStore , avec deux attributs d'état : user , pour l'utilisateur actuellement connecté, et error , pour l'erreur actuelle liée à la connexion. Dans l'esprit de réduire le passe-partout, Alt nous permet de nous lier à toutes les actions d'une classe avec une seule fonction bindActions .

 class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...

Les noms de gestionnaire sont définis par convention, en préfixe on nom d'action correspondant. Ainsi, l'action de login est gérée par onLogin , et ainsi de suite. Notez que la première lettre du nom de l'action sera en majuscule dans le style camelCase. Dans notre LoginStore , nous avons les gestionnaires suivants, appelés par les actions correspondantes :

 ... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } } onLogout() { this.user = null; this.error = null; } }

Composants

La manière habituelle de lier les magasins aux composants consiste à utiliser une sorte de mixin React. Mais comme les mixins ne sont plus à la mode, il doit y avoir un autre moyen. L'une des nouvelles approches consiste à utiliser des composants d'ordre supérieur. Nous prenons notre composant et le plaçons dans un composant wrapper, qui se chargera d'écouter les magasins et d'appeler le nouveau rendu. Notre composant recevra l'état du magasin dans les props . Cette approche est également utile pour organiser notre code en composants intelligents et stupides, qui sont devenus à la mode ces derniers temps. Pour Alt, le composant wrapper est implémenté par AltContainer :

 export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }

Notre composant LoginPage utilise également le décorateur changeHandler présenté précédemment. Les données de LoginStore sont utilisées pour afficher les erreurs en cas d'échec de connexion, et le re-rendu est pris en charge par AltContainer . Cliquer sur le bouton de connexion exécute l'action de login , complétant le flux de travail Alt flux :

 @changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }

Rendu isomorphe

Les applications Web isomorphes sont un sujet brûlant ces jours-ci car elles résolvent certaines des plus grandes tâches des applications traditionnelles à page unique. Dans ces applications, le balisage est créé dynamiquement par JavaScript dans le navigateur. Le résultat est que le contenu n'est pas disponible pour les clients avec JavaScript désactivé, notamment les robots d'indexation des moteurs de recherche. Cela signifie que votre page Web n'est pas indexée et n'apparaît pas dans les résultats de recherche. Il existe des moyens de contourner ce problème, mais ils sont loin d'être optimaux. L'approche isomorphe tente de résoudre ce problème en pré-affichant l'URL demandée d'une application à page unique sur le serveur. Avec Node.js, vous avez JavaScript sur le serveur, ce qui signifie que React peut également s'exécuter côté serveur. Cela ne devrait pas être trop difficile, non ?

Un obstacle est que certaines bibliothèques Flux, en particulier celles qui utilisent des singletons, ont des difficultés avec le rendu côté serveur. Lorsque vous avez des magasins Flux singleton et plusieurs requêtes simultanées vers votre serveur, les données seront mélangées. Certaines bibliothèques résolvent ce problème en utilisant des instances de Flux, mais cela s'accompagne d'autres inconvénients, en particulier la nécessité de transmettre ces instances dans votre code. Alt propose également des instances Flux, mais il a également résolu le problème du rendu côté serveur avec des singletons ; il vide les magasins après chaque demande, de sorte que chaque demande simultanée commence par une table rase.

Le cœur de la fonctionnalité de rendu côté serveur est fourni par React.renderToString . L'ensemble de l'application frontale React est également exécuté sur le serveur. De cette façon, nous n'avons pas besoin d'attendre que le JavaScript côté client crée le balisage ; il est pré-construit sur le serveur pour l'URL consultée et envoyé au navigateur au format HTML. Lorsque le client JavaScript s'exécute, il reprend là où le serveur s'est arrêté. Pour soutenir cela, nous pouvons utiliser la bibliothèque Iso, qui est destinée à être utilisée avec Alt.

Tout d'abord, nous initialisons Flux sur le serveur en utilisant alt.bootstrap . Il est possible de pré-remplir les magasins Flux avec des données pour le rendu. Il est également nécessaire de décider quel composant rendre pour quelle URL, qui est la fonctionnalité du côté client Router . Nous utilisons la version singleton de alt , donc après chaque rendu, nous devons alt.flush() les magasins pour les nettoyer pour une autre requête. À l'aide du module complémentaire iso , l'état de Flux est sérialisé dans le balisage HTML, afin que le client sache où aller :

 // We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; }; app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });

Côté client, nous récupérons l'état du serveur et amorçons alt avec les données. Ensuite, nous exécutons Router et React.render sur le conteneur cible, ce qui mettra à jour le balisage généré par le serveur si nécessaire.

 Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })

Beau!

Bibliothèques frontales utiles

Un guide de l'écosystème React ne serait pas complet sans mentionner quelques bibliothèques frontales qui fonctionnent particulièrement bien avec React. Ces bibliothèques s'attaquent aux tâches les plus courantes que l'on trouve dans presque toutes les applications Web : mises en page et conteneurs CSS, formulaires et boutons stylisés, validations, sélection de dates, etc. Il ne sert à rien de réinventer la roue lorsque ces problèmes ont déjà été résolus.

React-Bootstrap

React-Bootstrap Logo

Twitter's Bootstrap framework has become commonplace since it is of immense help to every web developer who does not want to spend a ton of time working in CSS. In the prototyping phase especially, Bootstrap is indispensable. To leverage the power of bootstrap in a React application, it's good to use it with React-Bootstrap, which comes with nice React syntax and re-implements Bootstrap's jQuery plugins using native React components. The resulting code is terse and easy to understand:

 <Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>

Personally, I cannot escape the feeling that this is what HTML should always have been like.

If you want to use Bootstrap source with Webpack, consider using less-loader or bootstrap-sass-loader, depending on the preprocessor you prefer. It will allow you to easily customize which Bootstrap components to include, and also allow the usage of LESS or SASS global variables in your CSS code.

React Router

React Router Logo

React Router has become the de-facto standard for routing in React. It allows nested routing, support for redirections, plays nicely with isomorphic rendering, and has a simple JSX-based syntax:

 <Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>

React Router also provides a Link component that you can use for navigation in your application, specifying only the route name:

 <nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>

There is even a library for integration with React-Bootstrap, so if you are using Bootstrap's components and don't feel like setting the active class on them manually all the time, you can use react-router-bootstrap and write code like this:

 <Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>

No additional setup is necessary. Active links will take care of themselves.

Formsy-React

Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us manage validations and data models. The Formsy-React library, strangely enough, does not include the actual form inputs because users are encouraged to write their own (which is great). But if you are content with the common ones, just use formsy-react-components. Bootstrap classes are included:

 import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }

Calendar and Typeahead

Calendar and typeahead are icing on the cake of every UI toolkit. Sadly, these two components were removed from Bootstrap 3, probably because they are too specialized for a general purpose CSS framework. Luckily, I've been able to find worthy replacements in react-pikaday and react-select. I've tested more than 10 libraries, and these two came out as the best. They are dead easy to use, as well:

 import Pikaday from 'react-pikaday'; import Select from 'react-select'; export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }
When it comes to React, it's a jungle out there! Here's a map to help you find your way.
Tweeter

Conclusion - React.JS

In this article I've presented libraries and techniques that I consider some of the most productive in current web development. Some of them are React-specific, but due to React's open nature, many of them are usable in other environments as well. Technological progress is sometimes hindered by fear of the newest stuff, so I hope this article will help to dissipate doubts concerning React, Flux and the newest features in ECMAScript.

React Ecosystem

If you are interested, you can take a look at my example application built with these technologies. The source code is available on GitHub.

Merci d'avoir lu!

Related: React.js Best Practices and Tips by Toptal Developers