de Michael Sargent

o bibliotecă de rutare este o componentă cheie a oricărei aplicații complexe, cu o singură pagină. Dacă dezvoltați aplicații web cu React și Redux, probabil că ați folosit sau cel puțin ați auzit de React Router. Este o bibliotecă de rutare bine cunoscută pentru React și o soluție excelentă pentru multe cazuri de utilizare.

dar routerul React nu este singura soluție viabilă din ecosistemul React/Redux. De fapt, există tone de soluții de rutare construite pentru React și pentru Redux, fiecare cu API — uri, caracteristici și obiective diferite-iar lista este în creștere. Inutil să spun că rutarea din partea clientului nu va dispărea în curând și există încă mult spațiu pentru proiectare în bibliotecile de rutare de mâine.

astăzi, vreau să vă atrag atenția asupra subiectului rutării în Redux. Voi prezenta și voi face un caz pentru Redux-first routing-o paradigmă care face din Redux Steaua modelului de rutare și firul comun printre multe soluții de rutare Redux. Voi demonstra cum să pun împreună API-ul de bază, cadru-agnostic în sub 100 de linii de cod, înainte de a explora opțiunile pentru utilizarea în lumea reală cu React și alte cadre front-end.

o mică istorie

în browser, locația (informațiile URL) și istoricul sesiunilor (un teanc de locații vizitate de fila curentă a browserului) sunt stocate în obiectul global window. Acestea sunt accesibile prin:

  • window.location (locație API)
  • window.history (Istorie API).

API-ul istoric oferă următoarele metode de navigare istoric, notabile pentru capacitatea lor de a actualiza istoricul și locația browserului fără a necesita o reîncărcare a paginii:

  • pushState(href) — împinge o nouă locație pe stiva de istorie
  • replaceState(href) — suprascrie locația curentă pe stivă
  • back() — navighează la locația anterioară din stivă
  • forward() — navighează la următoarea locație din stivă
  • go(index) — navighează într-o locație de pe stivă, în orice direcție.

împreună, API-urile de Istorie și locație permit paradigma modernă de rutare din partea clientului cunoscută sub numele de rutare pushState — primul protagonist al poveștii noastre.

acum, este aproape o crimă să menționăm API-urile de Istorie și locație fără a menționa o bibliotecă modernă de ambalaje precum history.

ReactTraining/istoric
Gestionați istoricul sesiunilor cu JavaScriptgithub.com

history oferă un API simplu, dar puternic pentru interfațarea cu istoricul și locația browserului, acoperind în același timp neconcordanțe între diferite implementări ale browserului. Este folosit ca dependență internă sau internă în multe biblioteci moderne de rutare și voi face mai multe referințe la acesta în acest articol.

rutare Redux și pushState

al doilea protagonist al poveștii noastre este Redux. Este 2017, așa că vă voi scuti de introducere și voi trece direct la subiect:

folosind rutarea simplă pushState într-o aplicație Redux, împărțim starea aplicației în două domenii: istoricul browserului și magazinul Redux.

Iată cum arată asta cu routerul React, care instanțiază și împachetează history:

history → React Router ↘ view Redux ↗

acum, știm că nu toate datele trebuie să locuiască în magazin. De exemplu, starea componentei locale este adesea un loc potrivit pentru a stoca date specifice unei singure componente.

dar datele despre locație nu sunt banale. Este o parte dinamică și importantă a stării aplicației — tipul de date care aparține magazinului. Ținându-l în magazin permite Luxurilor Redux, cum ar fi depanarea călătoriei în timp și accesul ușor de la orice componentă conectată la magazin.

Deci, cum mutăm locația în magazin?

nu se poate evita faptul că browserul citește și stochează istoricul și informațiile despre locație în window, dar ceea ce putem face este să păstrăm o copie a datelor despre locație în magazin și să o păstrăm sincronizată cu browserul.

nu asta face react-router-redux pentru routerul React?

Da, dar numai pentru a activa capacitățile de călătorie în timp ale Redux DevTools. Aplicația depinde în continuare de datele de locație deținute în React Router:

history → React Router ↘ ↕ view Redux ↗

utilizarea react-router-redux pentru a citi datele de locație din magazin în loc de React Router este descurajată (din cauza surselor de adevăr potențial conflictuale).

putem face mai bine?

putem construi un model de rutare alternativ-unul care este construit de la sol pentru a juca bine cu Redux, permițându — ne să citim și să actualizăm locația în modul Redux-cu store.getState() și store.dispatch()?

putem absolut, și se numește Redux-prima rutare.

Redux – prima rutare

Redux-prima rutare este o variație pe rutare pushState care face Redux Steaua modelului de rutare.

o soluție de rutare Redux-first îndeplinește următoarele criterii:

  • locația este ținută în magazinul Redux.
  • locația este modificată prin expedierea acțiunilor Redux.
  • aplicația citește datele de locație exclusiv din magazin.
  • Istoricul magazinului și al browserului sunt păstrate sincronizate în spatele scenei.

Iată o idee de bază despre cum arată:

history ↕ Redux → router → view

stai, nu există încă două surse de date despre locație?

Da, dar dacă putem avea încredere că istoricul browserului și magazinul Redux sunt sincronizate, putem construi aplicațiile noastre pentru a citi doar datele de locație din magazin. Apoi, din punctul de vedere al aplicației, există o singură sursă de adevăr — magazinul.

cum realizăm rutarea Redux-first?

putem începe prin crearea unui model conceptual, prin îmbinarea elementelor fundamentale ale modelelor de rutare pe partea clientului și ale ciclului de viață al datelor Redux.

revizuirea modelului de rutare din partea clientului

rutarea din partea clientului este un proces în mai multe etape care începe cu navigarea și se termină cu randarea — rutarea în sine este doar un pas în acest proces! Să examinăm detaliile:

  • navigare — totul începe cu o schimbare a locației. Există 2 tipuri de navigație: interne și externe. Navigarea internă se realizează din cadrul aplicației (de ex. prin API-ul istoric), în timp ce navigarea externă apare atunci când utilizatorul interacționează cu bara de navigare a browserului sau intră în aplicație de pe un site extern.
  • răspuns la Navigare — când locația se schimbă, aplicația răspunde trecând noua locație la router. Tehnicile de rutare mai vechi s-au bazat pe polling window.location pentru a realiza acest lucru, dar în zilele noastre avem utilitatea history.listen la îndemână.
  • rutare — apoi, noua locație este potrivită cu conținutul paginii corespunzătoare. Codul care gestionează acest pas se numește router și, în general, necesită un parametru de intrare pentru potrivirea rutelor și paginilor numit configurație de rută.
  • Rendering — în cele din urmă, conținutul este randat pe client. Acest pas poate fi, desigur, gestionat de un cadru/bibliotecă front-end precum React.

rețineți că bibliotecile de rutare nu trebuie să se ocupe de fiecare parte a modelului de rutare.

unele biblioteci, cum ar fi React Router și Vue Router, fac — în timp ce altele, cum ar fi universal Router, sunt preocupate exclusiv de un singur aspect( cum ar fi rutarea), oferind astfel flexibilitate în celelalte aspecte:

bibliotecile de rutare pot avea domenii diferite de responsabilitate. (Click pentru marire)

revizuirea modelului ciclului de viață al datelor Redux

Redux se mândrește cu un model de flux de date/ciclu de viață unidirecțional, care probabil nu are nevoie de nicio introducere — dar iată o scurtă prezentare generală pentru o măsură bună:

  • Acțiune — orice modificare a stării începe prin expedierea unei acțiuni Redux (un obiect simplu care conține o sarcină utilă type și opțională).
  • Middleware — acțiunea trece prin lanțul de magazine al magazinului, unde acțiunile pot fi interceptate și pot fi executate comportamente suplimentare. Middlewares sunt utilizate în mod obișnuit pentru a gestiona efectele secundare în aplicațiile Redux.
  • reductor-acțiunea ajunge apoi la reductorul rădăcină, care calculează starea următoare a magazinului ca o funcție pură a stării anterioare și a acțiunii primite. Reductorul rădăcină poate fi compus din reductoare individuale care se ocupă fiecare de o felie din starea magazinului.
  • Stare nouă — magazinul salvează noua stare returnată de reductor și notifică abonații săi despre modificare (în React, prin connect).
  • redare — în cele din urmă, vizualizarea conectată la magazin poate fi redată în conformitate cu noua stare.

construirea unui model de rutare Redux-First

natura unidirecțională a rutării din partea clientului și a modelelor ciclului de viață al datelor Redux se pretează bine unui model fuzionat care îndeplinește criteriile pe care le-am stabilit pentru rutarea Redux-first.

în acest model, routerul este abonat la magazin, navigarea se realizează prin acțiuni Redux, iar actualizările istoricului browserului sunt gestionate de un middleware personalizat. Să examinăm detaliile acestui model:

  • navigare internă prin acțiuni Redux — în loc să folosiți direct API-ul istoric, navigarea internă se realizează prin expedierea uneia dintre cele 5 acțiuni de navigare care reflectă metodele de navigare istoric.
  • actualizarea istoricului browserului prin middleware — un middleware este utilizat pentru a intercepta acțiunile de navigare și pentru a gestiona efectul secundar al actualizării istoricului browserului. Deoarece noua locație nu este neapărat sau ușor cunoscută fără a consulta mai întâi istoricul browserului (de ex. în cazul unei acțiuni go), acțiunile de navigație sunt împiedicate să ajungă la reductor.
  • răspuns la Navigare — fluxul de execuție continuă cu un ascultător history care răspunde la Navigare (atât din middleware, cât și din navigarea externă) prin expedierea unei a doua acțiuni care conține noua locație.
  • reductor de locație — acțiunea expediată de ascultător ajunge apoi la reductorul de locație, care adaugă locația în magazin. Reductorul de locație determină, de asemenea, forma stării locației.
  • rutare conectată — routerul conectat la magazin poate apoi să determine în mod reactiv noul conținut al paginii atunci când este notificat despre o modificare a locației din magazin.
  • Rendering — în cele din urmă, pagina poate fi re-randat cu noul conținut.

rețineți că acest lucru nu este singura modalitate de a realiza Redux-prima rutare — unele variante dispun de utilizarea unui potențiator de magazin și/sau logica suplimentare în middleware — dar este un model simplu care acoperă toate bazele.

o implementare de bază

urmând modelul pe care tocmai l — am analizat, să implementăm API-ul de bază-acțiunile, middleware-ul, ascultătorul și reductorul.

vom folosi pachetul history ca dependență internă și vom construi soluția treptat. Dacă preferați să urmați rezultatul final, îl puteți vedea aici.

acțiuni

vom începe prin definirea celor 5 acțiuni de navigare care reflectă metodele de navigare istoric:

// constants.jsexport const PUSH = 'ROUTER/PUSH';export const REPLACE = 'ROUTER/REPLACE';export const GO = 'ROUTER/GO';export const GO_BACK = 'ROUTER/GO_BACK';export const GO_FORWARD = 'ROUTER/GO_FORWARD';

// actions.jsexport const push = (href) => ({ type: PUSH, payload: href,});
export const replace = (href) => ({ type: REPLACE, payload: href,});
export const go = (index) => ({ type: GO, payload: index,});
export const goBack = () => ({ type: GO_BACK,});
export const goForward = () => ({ type: GO_FORWARD,});

Middleware

apoi, să definim middleware. Ar trebui să intercepteze acțiunile de navigare, să apeleze metodele de navigare corespunzătoare history, apoi să oprească acțiunea să ajungă la reductor — dar să lase toate celelalte acțiuni netulburate:

// middleware.jsexport const routerMiddleware = (history) => () => (next) => (action) => { switch (action.type) { case PUSH: history.push(action.payload); break; case REPLACE: history.replace(action.payload); break; case GO: history.go(action.payload); break; case GO_BACK: history.goBack(); break; case GO_FORWARD: history.goForward(); break; default: return next(action); }};

dacă nu ați avut șansa de a scrie sau de a examina internele unui middleware Redux înainte, consultați această introducere.

istoric ascultător

apoi, vom avea nevoie de un ascultător history care răspunde la navigare prin expedierea unei noi acțiuni care conține noile informații despre locație.

mai întâi, să adăugăm noul tip de acțiune și creatorul. Părțile interesante ale locației sunt pathname, search și

// constants.jsexport const LOCATION_CHANGE = 'ROUTER/LOCATION_CHANGE';
// actions.jsexport const locationChange = ({ pathname, search, hash }) => ({ type: LOCATION_CHANGE, payload: { pathname, search, hash, },});

atunci să scriem funcția de ascultător:

// listener.jsexport function startListener(history, store) { history.listen((location) => { store.dispatch(locationChange({ pathname: location.pathname, search: location.search, hash: location.hash, })); });}

vom face o mică adăugare – o expediere inițială locationChange , pentru a ține cont de intrarea inițială în aplicație (care nu este preluată de ascultătorul istoric):

// listener.jsexport function startListener(history, store) { store.dispatch(locationChange({ pathname: history.location.pathname, search: history.location.search, hash: history.location.hash, }));
 history.listen((location) => { store.dispatch(locationChange({ pathname: location.pathname, search: location.search, hash: location.hash, })); });}

reductor

apoi, să definim reductorul de locație. Vom folosi o formă simplă de stare și vom face o muncă minimă în reductor:

// reducer.jsconst initialState = { pathname: '/', search: '', hash: '',};

export const routerReducer = (state = initialState, action) => { switch (action.type) { case LOCATION_CHANGE: return { ...state, ...action.payload, }; default: return state; }};

cod de aplicare

în cele din urmă, să cârlig până API-ul nostru în codul de aplicare:

// index.jsimport { combineReducers, applyMiddleware, createStore } from 'redux'import { createBrowserHistory } from 'history'import { routerReducer } from './reducer'import { routerMiddleware } from './middleware'import { startListener } from './listener'import { push } from './actions'
// Create the history objectconst history = createBrowserHistory()
// Build the root reducerconst rootReducer = combineReducers({ // ...otherReducers, router: routerReducer,}) // Build the middlewareconst middleware = routerMiddleware(history)
// Create the storeconst store = createStore(rootReducer, {}, applyMiddleware(middleware))
// Start the history listenerstartListener(history, store)
// Now you can read location data from the store!let currentLocation = store.getState().router.pathname
// You can also subscribe to changes in the location!let unsubscribe = store.subscribe(() => { let previousLocation = currentLocation currentLocation = store.getState().router.pathname
 if (previousLocation !== currentLocation) { // You can render your application reactively here! }})
// And you can dispatch navigation actions from anywhere!store.dispatch(push('/about'))

și asta e tot acolo este să-l! Folosind API-ul nostru minuscul (sub 100 de linii de cod) ,am îndeplinit toate criteriile pentru rutarea Redux-first:

  • locația este ținută în magazinul Redux.
  • locatia se schimba prin expedierea actiunilor Redux.
  • aplicația citește datele de localizare exclusiv din magazin.
  • magazinul și istoricul browserului sunt păstrate sincronizate în spatele scenei. ✔

Vizualizați toate fișierele împreună aici — nu ezitați să le importați în proiectul dvs. sau să îl utilizați ca punct de plecare pentru a vă dezvolta propria implementare.

pachetul redux-first-routing

de asemenea, am pus API-ul împreună în pachetul redux-first-routing, pe care îl puteți npm install și îl puteți folosi în același mod.

mksarge/redux-first-routing
redux-first-routing — o bază minimă, cadru-agnostică pentru realizarea Redux-first routing.github.com

include o implementare similară cu cea pe care am construit-o aici, dar cu adăugarea notabilă de analiză a interogărilor prin intermediul pachetului query-string.

așteptați — cum rămâne cu componenta de rutare reală?

este posibil să fi observat că redux-first-routing este preocupat doar de aspectul de navigație al modelului de rutare:

prin decuplarea aspectului de navigație de celelalte aspecte ale modelului nostru de rutare, am câștigat o anumită flexibilitate — redux-first-routing este atât router-agnostic, cât și cadru-agnostic.

prin urmare, îl puteți asocia cu o bibliotecă precum Universal Router pentru a crea o soluție completă de rutare Redux-first pentru orice cadru front-end:

Faceți clic aici pentru a începe cu redux-first-routing + universal-router.

sau, ai putea construi legături dogmatic pentru Cadrul de alegere — și asta e ceea ce vom face pentru a reacționa în următoarea și ultima secțiune a acestui articol.

utilizare cu React

să terminăm explorarea noastră uitându-ne la modul în care am putea construi componente conectate la magazin pentru navigare declarativă și rutare în React.

navigare declarativă

pentru navigare, putem folosi o componentă conectată la magazin <Link/> similară cu cea din React Router și alte soluții de rutare React.

pur și simplu suprascrie comportamentul implicit al elementului de ancorare < a / > și dispecerhes o acțiune push atunci când faceți clic:

// Link.jsimport React from 'react';import { connect } from 'react-redux';import { push as pushAction, replace as replaceAction } from './actions';
const Link = (props) => { const { to, replace, children, dispatch, ...other } = props;
 const handleClick = (event) => { // Ignore any click other than a left click if ((event.button && event.button !== 0) || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || event.defaultPrevented === true) { return; } // Prevent the default behaviour (page reload, etc.) event.preventDefault();
 // Dispatch the appropriate navigation action if (replace) { dispatch(replaceAction(to)); } else { dispatch(pushAction(to)); } };
 return ( <a href={to} onClick={handleClick} {...other}> {children} </a>);};
export default connect()(Link);

puteți găsi o implementare mai completă aici.

rutare declarativă

deși nu există prea multe componente de navigație, există nenumărate modalități de a proiecta o componentă de rutare — ceea ce o face cea mai interesantă parte a oricărei soluții de rutare.

ce este un router, oricum?

în general, puteți vizualiza un router ca o funcție sau cutie neagră cu două intrări și o ieșire:

route configuration ↘ matched content current location ↗

deși rutarea și randarea ulterioară pot apărea în pași separați, React le face ușor și intuitiv să le îmbine într-un API de rutare declarativă. Să ne uităm la două strategii pentru realizarea acestui lucru.

strategia 1: o componentă monolitică <Route r / >

putem folosi o componentă monolitică, conectată la magazin <Router/> care:

  • acceptă un obiect de configurare a rutei prin recuzită
  • citește datele de locație din magazinul Redux
  • calculează conținutul nou ori de câte ori locația se schimbă
  • redă/redă conținutul după caz.

configurația rutei poate fi un obiect JavaScript simplu care conține toate căile și paginile potrivite (o configurație centralizată a rutei).

Iată cum ar putea arăta acest lucru:

const routes = 
React.render( <Provider store={store}> <Router routes={routes}> </Provider>, document.getElementById('app'))

destul de simplu, nu? Nu este nevoie de rute imbricate JSX — doar un singur obiect de configurare traseu, și o singură componentă router.

dacă această strategie vă atrage, consultați Implementarea mea mai completă în biblioteca redux-json-router. Acesta înfășoară redux-first-routing și oferă legături React pentru navigarea declarativă și rutare folosind strategiile pe care le-am examinat până acum.

mksarge/redux-JSON-router
redux-JSON-router – declarativ, Redux – prima rutare pentru browserul React/Redux applications.github.com

strategia 2: Composable <Route/> componente

în timp ce o componentă monolitică poate fi o modalitate simplă de a realiza rutarea declarativă în React, cu siguranță nu este singura cale.

natura composabilă a React permite o altă posibilitate interesantă: utilizarea JSX pentru a defini rutele într-o manieră descentralizată. Desigur, primul exemplu este react Router ‘ s <Route/> API:

React.render( <BrowserRouter> <Route path='/' component={Home}/> <Route path='/about component={About}/> ... </BrowserRouter>

alte biblioteci de rutare explorează și această idee. Deși nu am avut șansa să o fac, nu văd niciun motiv pentru care un API similar nu a putut fi implementat în partea de sus a pachetului redux-first-routing.

în loc să se bazeze pe datele de localizare furnizate de <BrowserRouter / >, the &l t;traseu / > componenta c ould simply conectați-vă la magazin:

React.render( <Provider store={store}> <Route path='/' component={Home}/> <Route path='/about component={About}/> ... </Provider>

dacă este ceva care vă interesează să construiți sau să utilizați, anunțați-mă în comentarii! Pentru a afla mai multe despre diferitele strategii de configurare a rutelor, consultați această introducere pe site-ul web al routerului React.

concluzie

sper că această explorare a contribuit la aprofundarea cunoștințelor dvs. despre rutarea din partea clientului și v-a arătat cât de simplu este să o realizați în modul Redux.

dacă sunteți în căutarea unei soluții complete de rutare Redux, puteți utiliza pachetul redux-first-routing cu un router compatibil listat în readme. Și dacă vă simțiți că trebuie să dezvoltați o soluție adaptată, sperăm că această postare v-a oferit un bun punct de plecare pentru a face acest lucru.

dacă doriți să aflați mai multe despre rutarea din partea clientului în React și Redux, consultați următoarele articole — acestea au fost esențiale pentru a mă ajuta să înțeleg mai bine subiectele pe care le-am acoperit aici:

  • lasă URL-ul să vorbească de Tyler Thompson
  • s-ar putea să nu ai nevoie de React Router de Konstantin Tarkus
  • am nevoie chiar de o bibliotecă de rutare? de James K. Nelson
  • și nenumărate discuții informative în problemele react-router-redux.

rutarea pe partea de Client este un spațiu cu posibilități de proiectare nesfârșite și sunt sigur că unii dintre voi v-ați jucat cu idei similare cu cele pe care le-am împărtășit aici. Dacă doriți să continuați conversația, voi fi bucuros să vă conectez în comentarii sau prin Twitter. Vă mulțumim pentru lectură!

edita 22/06/17: De asemenea, consultați acest articol despre redux-first-router, un proiect separat care utilizează tipuri de acțiuni inteligente pentru a obține capabilități puternice de rutare.

Lasă un răspuns

Adresa ta de email nu va fi publicată.