Com es crea la funció de cerca GitHub a React with RxJS 6 i Recompose

Aquesta publicació està dirigida a persones amb experiència React i RxJS. Només comparteixo patrons que he trobat útils per crear aquesta interfície d'usuari.

Estem construint el següent:

Sense classes, ganxos de cicle de vida ni setState.

configuració

Tot està al meu GitHub.

Clonar Git https://github.com/yazeedb/recompose-github-ui cd recompose-github-ui install yarn

La branca principal té el projecte acabat, així que comproveu la branca inicial si voleu fer-ne un seguiment.

Inicieu Git Checkout

I executeu el projecte.

npm d'inici

L’aplicació s’hauria d’executar a localhost: 3000. Aquí teniu la nostra interfície d’usuari inicial.

Obriu el projecte al vostre editor de text preferit i visualitzeu src / index.js.

Torneu a compondre

Si no l’heu vist abans, Recompose és un meravellós cinturó d’utilitat de React que us permet crear components amb un estil de programació funcional. Té moltes funcions i em costa escollir els meus favorits.

És Lodash / Ramda, però per React.

També crec que és fantàstic que admetin observables. Cita dels documents:

Resulta que gran part de l’API React Component es pot expressar en termes observables

Avui exercirem aquest concepte.

Transmissió del nostre component

De moment, l’aplicació és un component React comú. El podem retornar mitjançant un element observable mitjançant la funció componentFromStream de Recompose.

Aquesta funció inicialment fa un component nul i es torna a representar quan el nostre observable retorna un valor nou.

Un tret de configuració

Torneu a compondre els fluxos tal com es descriu a la proposta observable ECMAScript. Això descriu com se suposa que funcionen els observables quan finalment s’envien a navegadors moderns.

Tanmateix, fins que no estigui completament implementat, confiem en biblioteques com RxJS, xstream, most, Flyd, etc.

Recompose no sap quina biblioteca estem utilitzant, de manera que proporciona un setObservableConfig que es pot utilitzar per convertir ES Observables al format que necessitem.

Creeu un fitxer nou anomenat observableConfig.js a src.

Afegiu aquest codi perquè Recompose sigui compatible amb RxJS 6:

importació {de} de 'rxjs'; importar {setObservableConfig} de "recompose";
setObservableConfig ({fromESObservable: from});

Importeu-lo a index.js:

importació "./observableConfig";

I ja estem preparats!

Torna a compondre + RxJS

Importa componentFromStream.

importa reacciona per "reacciona"; importar ReactDOM des de 'react-dom'; importar {componentFromStream} de "recompose"; importació "./styles.css"; importació "./observableConfig";

Comenceu a redefinir l'aplicació amb aquest codi:

const App = componentFromStream (prop $ => {...});

Tingueu en compte que componentFromStream accepta una funció de devolució de trucada que espera un flux prop. La idea és que els nostres accessoris siguin observables i els assignem a un component React.

Si heu utilitzat RxJS, coneixereu l’operador perfecte per assignar valors.

mapa

Com el seu nom indica, es converteix observable (alguna cosa) en observable (una altra cosa). En el nostre cas, l’observable (atrezzo) es converteix en observable (component).

Importeu l'operador del mapa:

importar {mapa} de 'rxjs / operator';

I redefiniu l'aplicació:

const App = componentFromStream (prop $ => {return prop $ .pipe (map (() => { )))});

Des de RxJS 5 hem estat utilitzant canonades en lloc d’operadors de concatenació.

Deseu i reviseu la vostra interfície d'usuari, el mateix resultat.

Afegiu un gestor d'esdeveniments

Ara farem que les nostres aportacions siguin una mica més reactives.

Importeu createEventHandler des de Recompose.

import {componentFromStream, createEventHandler} des de "recompose";

I utilitzeu-ho així:

const App = componentFromStream (prop $ => {const {handler, stream} = createEventHandler ();
retorn prop $ .pipe (map (() => { )))});

createEventHandler és un objecte amb dues propietats interessants: handler i stream.

Sota el capó, el controlador és un emissor d’esdeveniments que transmet valors al flux. Es tracta d’una transmissió observable d’aquests valors als seus subscriptors.

Per tant, combinem el flux observable i el Prop $ observable per accedir al valor actual de l'entrada.

Les recol·lectores són una bona opció aquí.

Problema de pollastre i ous

Per utilitzar combineLatest, cal emetre tant stream com prop $. El flux no s'emet fins que s'emet el prop $ i viceversa.

Podem solucionar-ho donant a la transmissió un valor inicial.

Importeu l’operador startWith des de RxJS:

{map, startWith} importació des de 'rxjs / operator';

Creeu una variable nova per capturar el flux canviat.

const {gestor, corrent} = createEventHandler ();
valor const $ = stream.pipe (mapa (e => e.target.value), comenceu per (''));

Sabem que els fluxos generen esdeveniments des d’Inputs onChange. Per tant, assigneu cada esdeveniment al seu valor de text immediatament.

A més, inicialitzem el valor "$" com una cadena buida: un valor predeterminat adequat per a una entrada buida.

Combina-ho tot

Estem preparats per combinar aquests dos fluxos i importar combineLatest com a mètode de construcció en lloc de com a operador.

Importeu {CombineLatest} de 'rxjs';

També podeu importar l’operador de tap per comprovar els valors tal com apareixen:

{map, startWith, toqueu} importa des de 'rxjs / operator';

I utilitzeu-ho així:

const App = componentFromStream (prop $ => {const {handler, stream} = createEventHandler (); valor const $ = stream.pipe (mapa (e => e.target.value), comenceu per (''));
Retorna combineLatest (prop $, valor $). canonada (toqueu (console.warn), mapa (() => { )))});

A mesura que escriviu, es registra [props, value].

Component d'usuari

Aquest component s’encarrega d’obtenir / mostrar el nom d’usuari que li donem. L’aplicació rep el valor i l’assigna a una trucada AJAX.

JSX / CSS

Tot es basa en aquest gran projecte GitHub Cards. La majoria de les coses, especialment els estils, es copien / enganxen o es revisen perquè coincideixin amb React i accessoris.

Creeu una carpeta src / User i enganxeu aquest codi a User.css:

I aquest codi a src / User / Component.js:

El component només emplena una plantilla amb la resposta JSON estàndard de l'API GitHub.

El contenidor

Ara que el component "estúpid" no està disponible, fem el component "intel·ligent":

Aquí hi ha src / User / index.js:

importa reacciona per "reacciona"; importar {componentFromStream} de "recompose"; importar {debounceTime, filter, card, pluck} de 'rxjs / operator'; Importa component de "./component"; importació "./User.css";
const User = componentFromStream (prop $ => {const getUser $ = prop $ .pipe (debounceTime (1000), pluck ('User'), filter (user => user && user.length), map (user => ( {usuari} )));
torna getUser $; });
Exportar usuaris estàndard;

Definim l'usuari com un componentFromStream, que retorna un flux prop $ que a assignat.

debounceTime

Com que l'usuari rep els seus accessoris a través del teclat, no volem escoltar totes les emissions.

Quan l'usuari comença a escriure, debounceTime (1000) ometrà totes les emissions durant 1 segon. Aquest patró s’utilitza sovint en trucades anticipades.

arrencar

Aquest component espera prop.user en algun moment. arriba a l'usuari perquè no haguem de destruir els nostres accessoris cada vegada.

filtre

Assegura que l’usuari existeix i que no sigui una cadena buida.

mapa

Primer, introduïu l’usuari -Dia.

Connecteu

Importeu el component d'usuari a src / index.js:

Importeu usuaris de "./User";

I introduïu un valor com a usuari:

Retorna combineLatest (prop $, valor $). pipe (toqueu (console.warn), mapa (([atrezzo, valor]) => (
)));

Al cap d'1 segon, el vostre valor es mostrarà a la pantalla.

Bon començament, però en realitat hem d’aconseguir l’usuari.

Aconsegueix l’usuari

L'API d'usuari de GitHub està disponible a https://api.github.com/users/$ Genealogieuser}. Podem extreure-ho fàcilment en una funció auxiliar a User / index.js:

const formatUrl = user => "https://api.github.com/users/$ {user}";

Ara podem afegir la targeta (formatUrl) després del filtre:

Notareu que al cap d’un segon el punt final de l’API apareixerà a la pantalla:

Però hem de fer una sol·licitud d'API. Aquí vénen switchMap i ajax.

switchMap

SwitchMap també s’utilitza a la previsualització.

Suposem que l’usuari introdueix un nom d’usuari i que el traiem cap amunt a switchMap.

Què passa si l’usuari escriu alguna cosa nova abans de tornar el resultat? T'interessa la resposta anterior de l'API?

No

switchMap cancel·la la recuperació anterior i se centra en l'actual.

Àiax

RxJS ofereix la seva pròpia implementació d'Ajax, que funciona molt bé amb switchMap.

Utilitzeu-los

Importem tots dos. El meu codi té aquest aspecte:

importar {ajax} de 'rxjs / ajax'; importa {debounceTime, filter, map, pluck, switchMap} de 'rxjs / operator';

I utilitzeu-ho així:

const User = componentFromStream (prop $ => {const getUser $ = prop $ .pipe (debounceTime (1000), pluck ('User'), filter (user => user && user.length), card (formatUrl), switchMap ( url => ajax (url) .pipe (pluck ("resposta"), mapa (component))));
torna getUser $; });

Canvieu del nostre flux d'entrada a un flux de sol·licituds d'Ajax. Un cop finalitzada la sol·licitud, accediu a la resposta i assigneu-la al nostre component d'usuari.

Tenim un resultat!

Gestió d'errors

Proveu d'introduir un nom d'usuari que no existeix.

Fins i tot si el canvieu, la nostra aplicació no funciona. Heu d’actualitzar per aconseguir més usuaris.

És una mala experiència d’usuari, oi?

catchError

Amb l’operador catchError podem donar una resposta adequada a la pantalla en lloc d’interrompre-la desapercebuda.

Importeu-lo:

importar {catchError, debounceTime, filter, map, pluck, switchMap} de 'rxjs / operator';

I enganxeu-lo fins al final de la vostra cadena Ajax.

switchMap (url => ajax (url) .pipe (pluck ("response"), map (component), catchError (({response}) => alert (response.message))))

Almenys rebem comentaris, però ho podem fer millor.

Un component d'error

Creeu un component nou, src / Error / index.js.

importa reacciona per "reacciona"; const Error = ({answer, status}) => ( Vaja! {status}: {response.message} {status}: {response.message} Siusplau torna-ho a provar. ); Exporta un error estàndard;

Això mostra la resposta i l'estat de la nostra trucada AJAX.

Importem-lo a User / index.js:

Error d'importació de '../Error';

I des de RxJS:

importació {de} de 'rxjs';

Recordeu que la nostra devolució de trucada componentFromStream ha de retornar un observable. Ho aconseguim amb von.

Aquí teniu el nou codi:

ajax (url) .pipe (pluck ("resposta"), targeta (component), catchError (error => de ( )))

Simplement distribuïu l'objecte de defecte com a puntal del nostre component.

Ara, si comprovem la nostra interfície d’usuari:

Molt millor!

Un indicador de càrrega

Normalment, ara necessitem algun tipus d’administració estatal. Com podeu configurar un altre indicador de càrrega?

Abans d’arribar a setState, però, hauríeu de considerar si RxJS ens pot ajudar.

Els documents Recompose em van fer pensar en aquesta línia:

Combineu diversos fluxos en lloc de setState ().

Edició: inicialment feia servir BehaviorSubjects, però Matti Lankinen va respondre amb una manera brillant de simplificar aquest codi. Gràcies Matti!

Importeu l'operador de combinació.

Importa {merge} 'rxjs';

Quan es faci la sol·licitud, combinarem el nostre Ajax amb un flux de components de càrrega.

Dins de componentFromStream:

const Usuari = componentFromStream (prop $ => {const carregant $ = de ( Carrega ... ); const getUser $ = ...

Es va observar un senzill indicador de càrrega h3. I utilitzeu-ho així:

constant carregant $ = de ( Carrega ... );
const getUser $ = prop $ .pipe (debounceTime (1000), pluck ('user'), filter (user => user && user.length), card (formatUrl), switchMap (url => merge ($ is loaded, ajax (url) .pipe (pluck ("resposta"), targeta (component), catchError (error => de ( ))))));

M'encanta el proper. Quan truqueu a switchMap, combineu els observables $ i Ajax carregats.

Com que carregar $ és un valor estàtic, primer s’imprimeix. Tanmateix, un cop finalitzat l'Ajax asíncron, es mostra i es mostra a la pantalla.

Abans de fer la prova, podem importar l’operador de retard perquè la transició no passi massa ràpidament.

importar {catchError, debounceTime, delay, filter, map, pluck, switchMap, tap} de 'rxjs / operator';

I utilitzeu-lo just abans del mapa (component):

ajax (url) .pipe (pluck ("resposta"), delay (1500), card (component), catchError (error => de ( )))

El nostre resultat?

Em pregunto fins a quin punt s’ha d’agafar aquest patró i en quina direcció. Si us plau, deixeu un comentari i compartiu els vostres pensaments.

I recordeu de mantenir premut aquest botó de palmes. (Podeu pujar fins a 50!)

Fins la pròxima vegada.

Mind, Scared Bzadough http://yazeedb.com/