Navigazione nell'ecosistema React.js

Pubblicato: 2022-03-11

navigare nell'ecosistema react.js

La velocità dell'innovazione in JavaScript Land è così alta che alcune persone pensano addirittura che sia controproducente. Una biblioteca può passare dal giocattolo dei primi utilizzatori, allo stato dell'arte, all'obsolescenza nel giro di pochi mesi. Essere in grado di identificare uno strumento che rimarrà rilevante per almeno un altro anno sta diventando un'arte stessa.

Quando React.js è stato rilasciato due anni fa, stavo appena imparando Angular e ho rapidamente liquidato React come un'oscura libreria di modelli. Durante quei due anni, Angular ha davvero preso piede tra gli sviluppatori JavaScript ed è quasi diventato sinonimo di moderno sviluppo JS. Ho anche iniziato a vedere Angular in ambienti aziendali molto conservatori e ho dato per scontato il suo futuro luminoso.

Ma all'improvviso accadde una cosa strana. Sembra che Angular sia diventato una vittima dell'effetto Osborne, o "morte per pre-annuncio". Il team ha annunciato che, in primo luogo, Angular 2 sarà completamente diverso, senza un chiaro percorso di migrazione da Angular 1 e, in secondo luogo, che Angular 2 non sarà disponibile per un altro anno circa. Cosa dice a qualcuno che vuole iniziare un nuovo progetto web? Vuoi scrivere il tuo nuovo progetto in un framework che sarà reso obsoleto da una nuova versione?

Questa ansia tra gli sviluppatori ha giocato nelle mani di React, che stava cercando di affermarsi nella comunità. Ma React si è sempre commercializzato come la "V" in "MVC". Ciò ha causato una certa frustrazione tra gli sviluppatori web, che sono abituati a lavorare con framework completi. Come faccio a riempire i pezzi mancanti? Devo scrivere il mio? Devo usare solo una libreria esistente? Se si, quale?

Abbastanza sicuro, Facebook (creatori di React.js) aveva un altro asso nella manica: il flusso di lavoro Flux, che prometteva di riempire le funzioni "M" e "C" mancanti. Per rendere le cose ancora più interessanti, Facebook ha affermato che Flux è un "modello", non un framework, e la loro implementazione di Flux è solo un esempio del modello. Fedele alla loro parola, la loro implementazione era davvero semplicistica e prevedeva la scrittura di un sacco di prolissi e ripetitivi per far funzionare le cose.

La comunità open source è venuta in soccorso e un anno dopo abbiamo dozzine di librerie Flux e persino alcuni meta-progetti volti a confrontarle. Questa è una buona cosa; invece di rilasciare una struttura aziendale già pronta, Facebook è riuscita a suscitare interesse nella comunità e ha incoraggiato le persone a trovare le proprie soluzioni.

C'è un effetto collaterale interessante in questo approccio: quando devi combinare molte librerie diverse per ottenere il tuo framework completo, stai effettivamente sfuggendo al blocco del fornitore e le innovazioni che emergono durante la costruzione del tuo framework possono essere facilmente riutilizzate altrove.

Ecco perché le novità su React sono così interessanti; la maggior parte può essere prontamente riutilizzata in altri ambienti JavaScript. Anche se non hai intenzione di utilizzare React, dare un'occhiata al suo ecosistema è stimolante. Potresti voler semplificare il tuo sistema di build usando il potente, ma relativamente facile da configurare, pacchetto Webpack del bundle di moduli o iniziare a scrivere ECMAScript 6 e persino ECMAScript 7 oggi con il compilatore Babel.

In questo articolo, esaminerò alcune delle interessanti funzionalità e librerie disponibili. Quindi, esploriamo l'ecosistema React!

Reagisci uomo esploratore

Sistema di costruzione

Il sistema di compilazione è probabilmente la prima cosa di cui dovresti preoccuparti quando crei una nuova applicazione web. Il sistema di compilazione non è solo uno strumento per l'esecuzione di script, ma nel mondo JavaScript, di solito modella la struttura generale dell'applicazione. Le attività più importanti che un sistema di compilazione deve svolgere sono le seguenti:

  • Gestione delle dipendenze esterne ed interne
  • Esecuzione di compilatori e preprocessori
  • Ottimizzazione delle risorse per la produzione
  • Esecuzione del server Web di sviluppo, del visualizzatore di file e del ricaricatore del browser

Negli ultimi anni, il flusso di lavoro di Yeoman con Bower e Grunt è stato presentato come la sacra trinità del moderno sviluppo del frontend, risolvendo rispettivamente i problemi di generazione standard, gestione dei pacchetti e attività comuni in esecuzione, con il popolo più progressista che è passato di recente da Grunt a Gulp.

Nell'ambiente React, puoi tranquillamente dimenticarti di questi. Non che tu non possa usarli, ma è probabile che tu possa farla franca usando Webpack e il buon vecchio NPM. Come è possibile? Webpack è un bundle di moduli, che implementa la sintassi dei moduli CommonJS, comune nel mondo Node.js, anche nel browser. In realtà rende le cose più semplici poiché non è necessario imparare un altro gestore di pacchetti per il front-end; usi semplicemente NPM e condividi le dipendenze tra server e front-end. Inoltre, non è necessario affrontare il problema del caricamento dei file JS nell'ordine corretto perché è dedotto dalle importazioni di dipendenze specificate in ciascun file e l'intero sciame è concatenato correttamente a uno script caricabile.

Logo del pacchetto web
Pacchetto Web

Per rendere la cosa ancora più attraente, Webpack, a differenza del suo cugino maggiore Browserify, può gestire anche altri tipi di risorse. Ad esempio, con i caricatori, puoi trasformare qualsiasi file di asset in una funzione JavaScript che integra o carica il file di riferimento. Quindi, dimentica la preelaborazione manuale e il riferimento alle risorse da HTML. require solo i tuoi file CSS/SASS/LESS da JavaScript e Webpack si occupa del resto con un semplice file di configurazione. Webpack include anche un server Web di sviluppo e un osservatore di file. Inoltre, puoi utilizzare la chiave "scripts" in package.json per definire le battute della 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" } ... }

E questo è tutto ciò di cui hai bisogno per sostituire Gulp e Bower. Naturalmente, puoi ancora utilizzare Yeoman per generare un'applicazione standard. Non scoraggiarti quando non c'è un generatore Yeoman per le cose che desideri (le librerie più all'avanguardia spesso non ne hanno uno). Puoi ancora clonare un po 'di boilerplate da GitHub e hackerare.

Correlati: in che modo i componenti React semplificano i test dell'interfaccia utente

ECMAScript di domani, oggi

Il ritmo dello sviluppo del linguaggio JavaScript è notevolmente aumentato negli ultimi anni e, dopo un periodo di rimozione delle stranezze e stabilizzazione del linguaggio, ora vediamo l'arrivo di nuove potenti funzionalità. La bozza delle specifiche ECMAScript 6 (ES6) è stata finalizzata, e anche se non è stato ancora ufficializzato, sta già trovando un'adozione diffusa. Il lavoro su ECMAScript 7 (ES7) è in corso, ma molte delle sue funzionalità sono già state adottate dalle librerie più all'avanguardia.

Logo ECMAScript 7
ECMAScript 7

Com'è possibile? Forse pensi di non poter sfruttare queste nuove brillanti funzionalità JavaScript fino a quando non saranno supportate in Internet Explorer, ma ripensaci. I transpiler ES sono già diventati così onnipresenti che possiamo anche fare a meno di un adeguato supporto del browser. Il miglior transpiler ES disponibile in questo momento è Babel: prenderà il tuo codice ES6+ più recente e lo trasformerà in ES5 vaniglia, quindi puoi utilizzare qualsiasi nuova funzionalità ES non appena viene inventata (e implementata in Babel, cosa che di solito accade abbastanza rapidamente).

Logo Babele
Babele

Le più recenti funzionalità JavaScript sono utili in tutti i framework front-end e React è stato recentemente aggiornato per funzionare correttamente con le specifiche ES6 ed ES7. Queste nuove funzionalità dovrebbero eliminare molti mal di testa durante lo sviluppo con React. Diamo un'occhiata ad alcune delle aggiunte più utili e come possono avvantaggiare un progetto React. Più avanti, vedremo come utilizzare alcuni strumenti e librerie utili con React sfruttando questa sintassi migliorata.

Classi ES6

La programmazione orientata agli oggetti è un paradigma potente e ampiamente adottato, ma l'interpretazione di JavaScript è un po' esotica. La maggior parte dei framework front-end, siano essi Backbone, Ember, Angular o React, hanno quindi adottato modalità proprietarie per definire classi e creare oggetti. Ma con ES6, ora abbiamo classi tradizionali in JavaScript e ha semplicemente senso usarle invece di scrivere la nostra implementazione. Quindi, invece di:

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

possiamo scrivere:

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

Per un esempio più elaborato, considera questo codice, usando la vecchia sintassi:

 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> ); } });

E confronta con la versione 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> ); } }

Qui, i metodi del ciclo di vita di React getDefaultProps e getInitialState non sono più necessari. getDefaultProps diventa la variabile di classe statica defaultProps e lo stato iniziale è appena definito nel costruttore. L'unico inconveniente è che i metodi non sono più vincolati automaticamente, quindi è necessario utilizzare il bind quando si chiamano i gestori da JSX.

Decoratori

I decoratori sono una funzione utile di ES7. Consentono di aumentare il comportamento di una funzione o di una classe racchiudendola all'interno di un'altra funzione. Ad esempio, supponiamo che tu voglia avere lo stesso gestore di modifiche su alcuni dei tuoi componenti, ma non vuoi impegnarti nell'antipattern di ereditarietà. Puoi invece usare un decoratore di classe. Definiamo il decoratore come segue:

 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; }

La cosa importante qui è che la funzione addChangeHandler aggiunge la funzione changeHandler al prototipo della classe target.

Per applicare il decoratore, potremmo scrivere:

 MyClass = changeHandler(MyClass)

o più elegantemente, con sintassi ES7:

 @addChangeHandler class MyClass { ... }

Per quanto riguarda il contenuto della funzione changeHandler stessa, con l'assenza di React di associazione dati a due vie, lavorare con gli input in React può essere noioso. La funzione changeHandler cerca di renderlo più semplice. Il primo parametro specifica una key sull'oggetto di stato, che fungerà da oggetto di dati per l'input. Il secondo parametro è l'attributo in cui verrà salvato il valore del campo di input. Questi due parametri vengono impostati da JSX utilizzando la parola chiave 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')} /> ) } }

Quando l'utente modifica il campo nome utente, il suo valore viene salvato in this.state.login.username , senza la necessità di definire più gestori personalizzati.

Funzioni delle frecce

La dinamica di JavaScript in this contesto è stata una seccatura costante per gli sviluppatori perché, in modo alquanto non intuitivo, this contesto di una funzione nidificata viene reimpostato su globale, anche all'interno di una classe. Per risolvere this problema, di solito è necessario salvarlo in una variabile di ambito esterna (di solito _this ) e usarla nelle funzioni interne:

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

Con la nuova sintassi ES6, la function(x){ può essere riscritta come (x) => { . Questa definizione del metodo "freccia" non solo this associa correttamente all'ambito esterno, ma è anche notevolmente più breve, il che conta sicuramente quando si scrive molto codice asincrono.

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

Incarichi di destrutturazione

Le assegnazioni di destrutturazione, introdotte in ES6, consentono di avere un oggetto composto sul lato sinistro di un'assegnazione:

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

Questo è carino, ma come ci aiuta effettivamente in React? Considera il seguente esempio:

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

Con la destrutturazione, puoi salvare alcune sequenze di tasti. Il valore letterale delle chiavi {url, method, params} viene assegnato automaticamente dai valori dell'ambito con gli stessi nomi delle chiavi. Questo idioma è usato abbastanza spesso e l'eliminazione della ripetizione rende il codice meno soggetto a errori.

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

La destrutturazione può anche aiutarti a caricare solo un sottoinsieme di un modulo:

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

Argomenti: Default, Rest e Spread

Gli argomenti delle funzioni sono più potenti in ES6. Infine, puoi impostare l'argomento predefinito :

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

Stanco di pasticciare con l'oggetto ingombrante degli arguments ? Con la nuova specifica, puoi ottenere il resto degli argomenti come un array:

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

E se non ti piace chiamare apply() , puoi semplicemente diffondere un array in argomenti di funzione:

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

Generatori e funzioni asincrone

ES6 ha introdotto i generatori di JavaScript. Un generatore è fondamentalmente una funzione JavaScript la cui esecuzione può essere sospesa e ripresa in seguito, ricordandone lo stato. Ogni volta che viene rilevata la parola chiave yield , l'esecuzione viene sospesa e l'argomento yield viene restituito all'oggetto chiamante:

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

Ecco un esempio di questo generatore in azione:

 > 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 }

Quando chiamiamo la funzione generatore, viene eseguita fino al primo yield , quindi si interrompe. Dopo aver chiamato next() , restituisce il primo valore e riprende l'esecuzione. Ogni yield restituisce un altro valore, ma dopo la terza chiamata, la funzione del generatore termina e ogni successiva chiamata a next() restituirà { value: undefined, done: true } .

Naturalmente, lo scopo dei generatori non è creare sequenze numeriche elaborate. La parte interessante è la loro capacità di interrompere e riprendere l'esecuzione della funzione, che può essere utilizzata per controllare il flusso di programma asincrono e infine eliminare quelle fastidiose funzioni di callback.

Per dimostrare questa idea, abbiamo prima bisogno di una funzione asincrona. Di solito, avremmo alcune operazioni di I/O, ma per semplicità, usiamo semplicemente setTimeout e restituiamo una promessa. (Nota che ES6 ha anche introdotto promesse native in JavaScript.)

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

Successivamente, abbiamo bisogno di una funzione consumer:

 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(); }

Questa funzione prende qualsiasi generatore come argomento e continua a chiamarlo next() finché ci sono valori da yield . In questo caso, i valori restituiti sono promesse, quindi è necessario attendere che le promesse si risolvano e utilizzare la ricorsione con loop() per ottenere il ciclo tra funzioni nidificate.

Il valore restituito viene risolto nel gestore then() e passato a value , che è definito nell'ambito esterno e che verrà passato alla chiamata next(value) . Questa chiamata rende il valore un risultato dell'espressione di rendimento corrispondente. Ciò significa che ora siamo in grado di scrivere in modo asincrono senza alcun callback:

 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);

Il generatore myGenerator verrà messo in pausa ad ogni yield , in attesa che il consumatore mantenga la promessa risolta. E infatti, vedremo i numeri calcolati apparire nella console a intervalli di un secondo.

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

Questo dimostra il concetto di base, tuttavia, non ti consiglio di utilizzare questo codice in produzione. Scegli invece una libreria ben collaudata come co. Ciò ti consentirà di scrivere facilmente codice asincrono con rendimenti, inclusa la gestione degli errori:

 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); });

Quindi, questo esempio mostra come scrivere codice asincrono senza callback usando i generatori ES6. ES7 fa un ulteriore passo avanti in questo approccio introducendo le parole chiave async e await ed eliminando del tutto la necessità di una libreria del generatore. Con questa capacità, l'esempio precedente sarebbe simile a questo:

 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); } };

A mio parere, questo elimina il dolore di lavorare con codice asincrono in JavaScript. Non solo in React, ma anche ovunque.

I generatori non sono solo più concisi e diretti, ma ci consentono anche di utilizzare tecniche che sarebbero molto difficili da implementare con i callback. Un importante esempio di bontà del generatore è la libreria middleware koa per Node.js. Mira a sostituire Express e, a tal fine, viene fornito con una caratteristica killer: la catena del middleware scorre non solo a valle (con richiesta del client), ma anche a monte , consentendo ulteriori modifiche alla risposta del server. Considera il seguente esempio di server koa:

 // 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

Il middleware di risposta yield s a valle nel gestore della risposta, che imposta il corpo della risposta, e nel flusso a monte (dopo l'espressione di yield ), è consentita un'ulteriore modifica di this.body , così come altre funzioni come la registrazione del tempo, che è possibile perché l'upstream e il downstream condividono lo stesso contesto di funzione. Questo è molto più potente di Express, in cui un tentativo di ottenere la stessa cosa finirebbe in questo modo:

 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);

Probabilmente puoi già individuare cosa c'è che non va qui; l'utilizzo di una variabile di start "globale" risulterà in una condizione di competizione, restituendo sciocchezze con richieste simultanee. La soluzione è una soluzione alternativa non ovvia e puoi dimenticarti di modificare la risposta nel flusso a monte.

Inoltre, quando usi koa, otterrai gratuitamente il flusso di lavoro asincrono del generatore:

 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);

Puoi immaginare le promesse e i richiami coinvolti nel ricreare questo piccolo esempio in Express.

Node.js Logo

In che modo tutto questo discorso di Node.js si collega a React? Bene, Node è la prima scelta quando si considera un back-end adatto per React. Poiché anche Node è scritto in JavaScript, supporta la condivisione del codice tra back-end e front-end, consentendoci di creare applicazioni Web React isomorfe. Ma ne parleremo più avanti.

Libreria Flusso

React è ottimo per creare componenti di visualizzazione componibili, ma abbiamo bisogno di un modo per gestire i dati e lo stato nell'intera applicazione. È stato quasi universalmente riconosciuto che React è il miglior complemento dell'architettura dell'applicazione Flux. Se sei completamente nuovo di Flux, ti consiglio un rapido aggiornamento.

Logo Flusso
Flusso

Ciò che non è stato così universalmente concordato è quale delle tante implementazioni Flux scegliere. Facebook Flux sarebbe la scelta più ovvia, ma per la maggior parte delle persone è troppo prolisso. Le implementazioni alternative si concentrano principalmente sulla riduzione della quantità di boilerplate richiesta con un approccio di convenzione sulla configurazione e anche con alcune funzioni utili per componenti di ordine superiore, rendering lato server e così via. Alcuni dei migliori contendenti, con varie metriche di popolarità, possono essere visti qui. Ho esaminato Alt, Reflusso, Flummox, Fluxxor e Marty.js.

Il mio modo di scegliere la libreria giusta non è in alcun modo oggettivo, ma potrebbe comunque aiutare. Fluxxor è stata una delle prime librerie che ho controllato, ma ora sembra un po' datata. Marty.js è interessante e ha molte funzionalità, ma comporta ancora molto standard e alcune funzioni sembrano superflue. Il reflusso ha un bell'aspetto e ha una certa trazione, ma sembra un po' difficile per i principianti e manca anche di una documentazione adeguata. Flummox e Alt sono molto simili, ma Alt sembra avere ancora meno standard, uno sviluppo molto attivo, una documentazione aggiornata e un'utile community Slack. Pertanto, ho scelto Alt.

Flusso alternativo

Logo Flusso alternativo
Flusso alternativo

Con Alt, il flusso di lavoro Flux diventa molto più semplice senza perdere la sua potenza. La documentazione Flux di Facebook dice molto sul dispatcher, ma siamo liberi di ignorarlo perché, in Alt, il dispatcher è implicitamente collegato alle azioni per convenzione e di solito non richiede alcun codice personalizzato. Questo ci lascia solo negozi , azioni e componenti . Questi tre livelli possono essere utilizzati in modo tale da essere mappati bene nel modello di pensiero MVC : i negozi sono modelli , le azioni sono controller e i componenti sono viste . La differenza principale è il flusso di dati unidirezionale centrale per il modello Flux, il che significa che i controller (azioni) non possono modificare direttamente le viste (componenti), ma, invece, possono solo attivare modifiche del modello (memorizza), a cui le viste sono legate passivamente. Questa era già una best practice per alcuni sviluppatori Angular illuminati.

Flusso e flussi di lavoro alternativi

Il flusso di lavoro è il seguente:

  1. I componenti avviano azioni.
  2. Gli archivi ascoltano le azioni e aggiornano i dati.
  3. I componenti sono vincolati agli archivi e vengono visualizzati nuovamente quando i dati vengono aggiornati.

Azioni

Quando si utilizza la libreria Alt Flux, le azioni generalmente sono di due tipi: automatico e manuale. Le azioni automatiche vengono create utilizzando la funzione generateActions e vanno direttamente al dispatcher. I metodi manuali sono definiti come metodi delle tue classi di azione e possono andare al dispatcher con un carico utile aggiuntivo. Il caso d'uso più comune delle azioni automatiche è notificare ai negozi alcuni eventi nell'applicazione. Le azioni manuali sono, tra le altre cose, il modo preferito per gestire le interazioni del server.

Quindi le chiamate API REST appartengono alle azioni. Il flusso di lavoro completo è il seguente:

  1. Il componente attiva un'azione.
  2. Il creatore dell'azione esegue una richiesta server asincrona e il risultato va al dispatcher come payload.
  3. L'archivio ascolta l'azione, il corrispondente gestore dell'azione riceve il risultato come argomento e l'archivio aggiorna il suo stato di conseguenza.

Per le richieste AJAX, possiamo utilizzare la libreria axios, che, tra le altre cose, gestisce senza problemi dati e intestazioni JSON. Invece di promesse o callback, possiamo utilizzare il modello ES7 async / await . Se lo stato della risposta POST non è 2XX, viene generato un errore e inviamo i dati restituiti o l'errore ricevuto.

Diamo un'occhiata a una pagina di accesso per un semplice esempio del flusso di lavoro Alt. L'azione di logout non deve fare nulla di speciale, solo notificare il negozio, quindi possiamo generarla automaticamente. L'azione di accesso è manuale e prevede i dati di accesso come parametro per il creatore dell'azione. Dopo aver ricevuto una risposta dal server, inviamo i dati di successo o, se viene generato un errore, inviamo l'errore ricevuto.

 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));

I negozi

Il negozio Flux ha due scopi: ha gestori di azioni e trasporta lo stato. Continuiamo il nostro esempio di pagina di accesso per vedere come funziona.

Creiamo LoginStore , con due attributi di stato: user , per l'utente che ha effettuato l'accesso corrente, ed error , per l'errore relativo all'accesso corrente. Nello spirito di riduzione standard, Alt ci consente di associare tutte le azioni di una classe con una singola funzione bindActions .

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

I nomi dei gestori sono definiti per convenzione, anteponendo on nome dell'azione corrispondente. Quindi l'azione di login è gestita da onLogin e così via. Si noti che la prima lettera del nome dell'azione sarà in maiuscolo in stile camelCase. Nel nostro LoginStore abbiamo i seguenti gestori, chiamati dalle azioni corrispondenti:

 ... 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; } }

Componenti

Il solito modo per legare i negozi ai componenti è usare una sorta di mixin React. Ma dal momento che i mixin stanno passando di moda, ci deve essere un altro modo. Uno dei nuovi approcci consiste nell'utilizzare componenti di ordine superiore. Prendiamo il nostro componente e lo inseriamo all'interno di un componente wrapper, che si occuperà di ascoltare i negozi e chiamare il re-rendering. Il nostro componente riceverà lo stato del negozio in props di scena. Questo approccio è utile anche per organizzare il nostro codice in componenti intelligenti e stupidi, che sono diventati di moda ultimamente. Per Alt, il wrapper del componente è implementato da AltContainer :

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

Il nostro componente LoginPage utilizza anche il decoratore changeHandler introdotto in precedenza. I dati di LoginStore vengono utilizzati per visualizzare errori in caso di accesso non riuscito e il re-rendering è curato da AltContainer . Facendo clic sul pulsante di accesso viene eseguita l'azione di login , completando il flusso di lavoro del flusso alternativo:

 @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> )} }

Rendering isomorfo

Le applicazioni web isomorfe sono un argomento caldo in questi giorni perché risolvono alcuni dei più grandi compiti delle tradizionali applicazioni a pagina singola. In tali applicazioni, il markup viene creato dinamicamente da JavaScript nel browser. Il risultato è che il contenuto non è disponibile per i client con JavaScript disattivato, in particolare i web crawler dei motori di ricerca. Ciò significa che la tua pagina web non è indicizzata e non appare nei risultati di ricerca. Ci sono modi per aggirare questo problema, ma sono tutt'altro che ottimali. L'approccio isomorfo tenta di risolvere questo problema eseguendo il pre-rendering dell'URL richiesto di un'applicazione a pagina singola sul server. Con Node.js, hai JavaScript sul server, il che significa che React può anche essere eseguito sul lato server. Non dovrebbe essere troppo difficile, giusto?

Un ostacolo è che alcune librerie Flux, specialmente quelle che usano singleton, hanno difficoltà con il rendering lato server. Quando hai archivi Flux singleton e più richieste simultanee al tuo server, i dati verranno confusi. Alcune librerie risolvono questo problema utilizzando le istanze Flux, ma ciò comporta altri inconvenienti, in particolare la necessità di passare quelle istanze nel codice. Alt offre anche istanze Flux, ma ha anche risolto il problema del rendering lato server con singleton; svuota gli archivi dopo ogni richiesta, in modo che ogni richiesta simultanea inizi con una tabula rasa.

Il nucleo della funzionalità di rendering lato server è fornito da React.renderToString . Anche l'intera applicazione front-end React viene eseguita sul server. In questo modo, non è necessario attendere che il JavaScript lato client crei il markup; è precostruito sul server per l'URL a cui si accede e inviato al browser come HTML. Quando il client JavaScript viene eseguito, riprende da dove il server si è interrotto. Per supportare questo, possiamo usare la libreria Iso, che è pensata per essere usata con Alt.

Innanzitutto, inizializziamo Flux sul server utilizzando alt.bootstrap . È possibile preriempire gli archivi Flux con i dati per il rendering. È anche necessario decidere quale componente rendere per quale URL, che è la funzionalità del lato client Router . Stiamo usando la versione singleton di alt , quindi dopo ogni rendering, dobbiamo alt.flush() i negozi per averli puliti per un'altra richiesta. Utilizzando il componente aggiuntivo iso , lo stato di Flux viene serializzato nel markup HTML, in modo che il client sappia dove prelevare:

 // 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()}); });

Sul lato client, prendiamo lo stato del server e avviamo il alt con i dati. Quindi eseguiamo Router e React.render sul contenitore di destinazione, che aggiornerà il markup generato dal server secondo necessità.

 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) }) })

Bello!

Utili librerie di front-end

Una guida all'ecosistema React non sarebbe completa senza menzionare alcune librerie front-end che funzionano particolarmente bene con React. Queste librerie affrontano le attività più comuni che si trovano in quasi tutte le applicazioni Web: layout e contenitori CSS, moduli e pulsanti con stile, convalide, selezione della data e così via. Non ha senso reinventare la ruota quando quei problemi sono già stati risolti.

React-Bootstrap

Logo React-Bootstrap

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.
Twitta

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.

Grazie per aver letto!

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