Av Michael Sargent

et rutebibliotek er en nøkkelkomponent i et komplekst, enkeltsideprogram. Hvis du utvikler webapper Med React og Redux, har du sikkert brukt, Eller i det minste hørt Om React Router. Det er et velkjent rutebibliotek For React, og en flott løsning for mange brukstilfeller.

Men React Router er ikke den eneste levedyktige løsningen I React/Redux-økosystemet. Faktisk er det tonnevis av rutingsløsninger bygget for React og For Redux, hver med forskjellige Apier, funksjoner og mål — og listen vokser bare. Unødvendig å si, ruting på klientsiden går ikke bort når som helst snart, og det er fortsatt mye plass til design i morgendagens rutebiblioteker.

I Dag vil jeg ta oppmerksomheten til emnet for ruting i Redux. Jeg vil presentere Og lage en sak For Redux-first routing – et paradigme som gjør Redux stjernen i rutemodellen, og den vanlige tråden blant Mange Redux-rutingsløsninger. Jeg skal demonstrere hvordan man setter sammen kjernen, rammeverk-agnostisk API i under 100 linjer med kode, før du utforsker alternativer for bruk i den virkelige verden med React og andre front-end-rammer.

En Liten Historie

i nettleseren lagres plasseringen (URL-informasjon) og økthistorikken (en stabel med steder besøkt av gjeldende nettleserfane) i det globale window – objektet. De er tilgjengelige via:

  • window.location (Sted API)
  • window.history (Historie API).

Historie API tilbyr følgende historie navigasjonsmetoder, kjent for sin evne til å oppdatere nettleserens historie og plassering uten å nødvendiggjøre en side reload:

  • pushState(href) — skyver en ny plassering på historikkstakken
  • replaceState(href) — overskriver gjeldende plassering på stakken
  • back() — navigerer til forrige plassering på stakken
  • forward() — navigerer til neste sted på stakken
  • go(index) — navigerer til et sted på stakken, i begge retninger.

Historikk-Og Plasserings-Api-Ene aktiverer Det moderne rutingsparadigmet på klientsiden, kjent som pushState-ruting — den første hovedpersonen i historien vår.

Nå er det nesten en forbrytelse å nevne Historie Og Sted Apier uten å nevne et moderne wrapper bibliotek som history.

ReactTraining/historikk
Administrer økthistorikk med JavaScriptgithub.com

history gir en enkel, men kraftig API for grensesnitt med nettleserens historie og plassering, mens dekker uoverensstemmelser mellom ulike nettleser implementeringer. Den brukes som en peer eller intern avhengighet i mange moderne rutebiblioteker, og jeg vil gjøre flere referanser til det gjennom denne artikkelen.

Redux Og pushState Ruting

den andre hovedpersonen i vår historie Er Redux. Det er 2017, så jeg sparer deg introduksjonen og får rett til poenget:

ved å bruke vanlig pushState-ruting i Et Redux-program, deler vi applikasjonsstaten over to domener: nettleserhistorikk og Redux-butikken.

Her er hva som ser ut med React Router, som instantiates og wraps history:

history → React Router ↘ view Redux ↗

nå vet Vi At ikke alle data må ligge i butikken. For eksempel er lokal komponentstatus ofte et egnet sted å lagre data som er spesifikke for en enkelt komponent.

men posisjonsdata er ikke trivielle. Det er en dynamisk og viktig del av programtilstanden – den typen data som tilhører butikken. Å holde den i butikken gjør Redux luksus som tidsreiser feilsøking, og enkel tilgang fra enhver butikk-tilkoblet komponent.

så hvordan flytter vi plasseringen inn i butikken?

det er ingen vei rundt det faktum at nettleseren leser og lagrer historie og stedsinformasjon i window, men det vi kan gjøre er å beholde en kopi av stedsinformasjonen i butikken, og holde den synkronisert med nettleseren.

Er det ikke hva react-router-redux gjør For React Router?

Ja, Men bare for å aktivere tidsreisefunksjonene Til Redux DevTools. Søknaden er fortsatt avhengig av stedsdata holdt I React Router:

history → React Router ↘ ↕ view Redux ↗

Bruk av react-router-redux for å lese posisjonsdata fra butikken i stedet For React Router frarådes (på grunn av potensielt motstridende sannhetskilder).

Kan vi gjøre det bedre?

Kan vi bygge en alternativ rutemodell-en som er bygget fra grunnen av for å spille bra med Redux, slik at vi kan lese og oppdatere plasseringen Redux — måten-med store.getState() og store.dispatch()?

vi kan absolutt, Og det kalles Redux-first routing.

Redux-Første Ruting

Redux-forste routing er en variasjon pa pushState routing som gjor Redux star for rutemodellen.

En Redux-første rutingsløsning oppfyller følgende kriterier:

  • plasseringen holdes i Redux-butikken.
  • plasseringen endres ved å sende Redux-handlinger.
  • programmet leser posisjonsdata utelukkende fra butikken.
  • lagre-og nettleserloggen holdes synkronisert bak kulissene.

her er en grunnleggende ide om hvordan det ser ut:

history ↕ Redux → router → view

Vent, er det ikke fortsatt to kilder til posisjonsdata?

Ja, men hvis vi kan stole på at nettleserloggen og Redux-butikken er synkronisert, kan vi bygge våre applikasjoner for å bare lese posisjonsdata fra butikken. Så, fra programmets synspunkt, er det bare en kilde til sannhet-butikken.

hvordan oppnår Vi Redux-første ruting?

Vi kan starte med å lage en konseptuell modell, ved å slå sammen de grunnleggende elementene i klientsiden ruting og Redux data lifecycle modeller.

Revidere Rutemodellen På Klientsiden

Client-side routing Er en multi-trinns prosess som starter med navigasjon og slutter med rendering — routing selv er bare ett trinn i den prosessen! La oss se gjennom detaljene:

  • Navigasjon-Alt starter med en endring i plassering. Det finnes 2 typer navigasjon: intern og ekstern. Intern navigasjon oppnås fra appen (f.eks. Via History API), mens ekstern navigasjon oppstår når brukeren samhandler med nettleserens navigasjonsfelt eller går inn i programmet fra et eksternt nettsted.
  • Svare på navigasjon – når plasseringen endres, svarer programmet ved å sende den nye plasseringen til ruteren. Eldre rutingsteknikker stod på avstemning window.location for å oppnå dette, men i dag har vi det praktiske history.listen verktøyet.
  • Ruting-deretter matches den nye plasseringen til det tilsvarende sideinnholdet. Koden som håndterer dette trinnet kalles en ruter, og det tar vanligvis en inngangsparameter for matchende ruter og sider som kalles en rutekonfigurasjon.
  • Gjengivelse-Til Slutt gjengis innholdet på klienten. Dette trinnet kan selvfølgelig håndteres av et front-end rammeverk / bibliotek som React.

Merk at rutebiblioteker ikke trenger å håndtere alle deler av rutemodellen.

Noen biblioteker, Som React Router og Vue Router, gjør — mens Andre, som Universal Router, bare er opptatt av et enkelt aspekt (som ruting), og gir dermed fleksibilitet i de andre aspektene:

Rutebiblioteker kan ha forskjellige ansvarsområder. (Klikk for å forstørre)

Revisjon Av Redux Data Lifecycle Model

Redux har en enveis dataflyt / livssyklusmodell som sannsynligvis ikke trenger introduksjon — men her er en kort oversikt for godt mål:

  • Handling – enhver endring i tilstand starter ved å sende En Redux-handling(et vanlig objekt som inneholder en type og valgfri nyttelast).
  • Mellomvare-handlingen går gjennom butikkens kjede av mellomvarer, hvor handlinger kan fanges opp og ytterligere oppførsel kan utføres. Middlewares brukes ofte til å håndtere bivirkninger I Redux-applikasjoner.
  • Reducer-handlingen når deretter rotreduksjonen, som beregner butikkens neste tilstand som en ren funksjon av forrige tilstand og mottatt handling. Roten redusering kan være sammensatt av individuelle reduksjonsgir som hver håndtere en bit av butikkens tilstand.
  • Ny tilstand-butikken lagrer den nye staten returnert av redusering, og varsler sine abonnenter av endringen (I React, via connect).
  • Rendering-endelig kan den butikk-tilkoblede visningen gjengis i samsvar med den nye tilstanden.

Bygge En Redux-Første Rutemodell

enveis natur klientsiden ruting og Redux data lifecycle modeller egner seg godt til en fusjonert modell som tilfredsstiller kriteriene vi lagt ut For Redux-første ruting.

i denne modellen abonnerer ruteren på butikken, navigasjonen utføres via Redux-handlinger, og oppdateringer til nettleserloggen håndteres av en tilpasset mellomvare. La oss undersøke detaljene i denne modellen:

  • Intern navigasjon via Redux-handlinger-i Stedet for Å bruke Historikk API direkte, oppnås intern navigasjon ved å sende en av 5 navigasjonshandlinger som speiler historikknavigasjonsmetodene.
  • Oppdatering av nettleserhistorikken via mellomvare – en mellomvare brukes til å fange opp navigasjonshandlingene og håndtere bivirkningen ved oppdatering av nettleserhistorikken. Siden den nye plasseringen ikke nødvendigvis eller lett kjent uten først å konsultere nettleserloggen (f.eks . i tilfelle av en go handling), blir navigasjonshandlingene forhindret i å nå reduksjonsmidlet.
  • Svare på navigasjon – utførelsesflyten fortsetter med en history lytter som reagerer på navigasjon (fra både mellomvare og ekstern navigasjon) ved å sende en annen handling som inneholder den nye plasseringen.
  • Location Reducer-handlingen sendt av lytteren når deretter location reducer, som legger plasseringen til butikken. Plasseringen redusering bestemmer også formen på plasseringen tilstand.
  • Tilkoblet ruting-den store-tilkoblede ruteren kan deretter reaktivt bestemme det nye sideinnholdet når den varsles om en endring i plassering i butikken.
  • Rendering-Til Slutt kan siden gjengis på nytt med det nye innholdet.

Merk at dette Ikke Er den eneste måten Å oppnå Redux-første ruting — noen variasjoner har bruk av en butikkforbedrer og / eller ekstra logikk i mellomvaren — men det er en enkel modell som dekker alle basene.

En Grunnleggende Implementering

Etter modellen vi nettopp så på, la oss implementere core API-handlingene, middleware, listener og reducer.

vi bruker pakken history som en intern avhengighet, og bygger løsningen trinnvis. Hvis du heller vil følge med på det endelige resultatet, kan du se det her.

Handlinger

vi begynner med å definere de 5 navigasjonshandlingene som speiler historikknavigasjonsmetodene:

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

Neste, la oss definere mellomvaren. Det bør avskjære navigasjonshandlingene, ring de tilsvarende history navigasjonsmetodene, og stopp deretter handlingen fra å nå reduksjonsmidlet – men la alle andre handlinger være uforstyrret:

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

hvis du ikke har hatt sjansen til å skrive eller undersøke internals av En Redux mellomvare før, sjekk ut denne introduksjonen.

History Listener

deretter trenger vi en history lytter som reagerer på navigasjon ved å sende en ny handling som inneholder den nye stedsinformasjonen.

La Oss først legge til den nye handlingstypen og skaperen. De interessante delene av plasseringen er pathname, search og hash — så det er det vi vil inkludere i nyttelasten:

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

så la oss skrive lytterfunksjonen:

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

Vi lager et lite tillegg-en innledende locationChange forsendelse, for å ta hensyn til den første oppføringen i søknaden (som ikke blir hentet av historikklytteren):

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

Reducer

Neste, la oss definere plasseringen redusering. Vi bruker en enkel tilstandsform, og gjør minimalt arbeid i reduksjonsmidlet:

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

Søknadskode

Endelig, La oss koble OPP VÅR API inn i programkoden:

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

Og det er alt det er til det! Ved hjelp av vår lille (under 100 linjer med kode) API, har vi møtt alle kriteriene For Redux-første ruting:

  • plasseringen holdes i Redux-butikken. ✔
  • plasseringen endres ved å sende Redux-handlinger. ✔
  • søknaden leser posisjonsdata utelukkende fra butikken. ✔
  • lagre-og nettleserhistorikken holdes synkronisert bak kulissene. ✔

Se alle filene sammen her – vær så snill å importere dem til prosjektet ditt, eller bruk det som utgangspunkt for å utvikle din egen implementering.

redux-first-routing-pakken

jeg har også satt API-en sammen i redux-first-routing – pakken, som du kan npm install og bruke på samme måte.

mksarge/redux-first-routing
redux-first-routing — en minimal, rammeverk-agnostisk base for å oppnå Redux-first routing.github.com

det inkluderer en implementering lik den vi bygget her, men med den bemerkelsesverdige tillegg av spørring parsing via query-string pakken.

Vent-hva med den faktiske rutingskomponenten?

du har kanskje lagt merke til at redux-first-routing bare er opptatt av navigasjonsaspektet av rutemodellen:

ved å koble navigasjonsaspektet fra de andre aspektene av rutemodellen vår, har vi fått litt fleksibilitet — redux-first-routing er både router-agnostiker og rammeagnostiker.

du kan derfor pare den med et bibliotek Som Universal Router for å lage en komplett Redux-første rutingsløsning for alle frontrammer:

Klikk her for å komme i gang med redux-first-routing + universal-router.

Eller du kan bygge sta bindinger for rammen av valget — og det er hva Vi skal gjøre For React i neste og siste delen av denne artikkelen.

Bruk Med React

la oss avslutte vår leting ved å se på hvordan vi kan bygge butikk-tilkoblede komponenter for deklarativ navigasjon og ruting I React.

Deklarativ Navigasjon

for navigasjon kan vi bruke en butikk-tilkoblet<Lin k/ > komponent som ligner Den I React Router og Andre React routing-løsninger.

det overstyrer bare standard oppførsel av ankerelement < a / > og dispatches en trykkhandling når den klikkes:

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

du kan finne en mer komplett implementering her.

Deklarativ Ruting

Selv om det ikke er mye til en navigasjonskomponent, er det utallige måter å designe en rutingskomponent på — noe som gjør den til den mest interessante delen av enhver rutingsløsning.

hva er en ruter, uansett?

du kan vanligvis se en ruter som en funksjon eller svart boks med to innganger og en utgang:

route configuration ↘ matched content current location ↗

Selv om rutingen og påfølgende gjengivelse kan skje i separate trinn, Gjør React det enkelt og intuitivt å pakke dem sammen til en deklarativ ruting API. La oss se på to strategier for å oppnå dette.

Strategi 1: en monolittisk <Route r / > komponent

Vi kan bruke en monolittisk, butikk-tilkoblet <Route r / > komponent som:

  • godtar et rutekonfigurasjonsobjekt via rekvisitter
  • leser posisjonsdataene fra Redux-butikken
  • beregner det nye innholdet når plasseringen endres
  • gjengir/gjengir innholdet etter behov.

rutekonfigurasjonen kan være et Vanlig JavaScript-objekt som inneholder alle de samsvarende banene og sidene (en sentralisert rutekonfigurasjon).

slik ser dette ut:

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

Ganske enkelt, ikke sant? Ingen behov for nestede jsx ruter-bare en enkelt rute konfigurasjonsobjekt, og en enkelt router komponent.

hvis denne strategien appellerer til deg, sjekk ut min mer komplette implementering i biblioteket redux-json-router. Den pakker redux-first-routing og gir React bindinger for deklarativ navigasjon og ruting ved hjelp av strategiene vi har undersøkt så langt.

mksarge/redux-json-router
redux-json-router-Deklarativ, Redux-første ruting For React / Redux nettleser applications.github.com

Strategi 2: Composable <Route / > components

mens en monolitisk komponent kan være en enkel måte å oppnå deklarativ ruting I React, er det definitivt ikke den eneste måten.

react composable natur tillater en annen interessant mulighet: å bruke JSX til å definere ruter på en desentralisert måte. Selvfølgelig er det viktigste eksempelet React Router ‘ s<Rout E/ > API:

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

andre rutebiblioteker utforsker også denne ideen. Selv om jeg ikke har hatt sjansen til å gjøre det, ser jeg ingen grunn til at en lignende API ikke kunne implementeres på toppen av redux-first-routing – pakken.

I Stedet for å stole på posisjonsdata levert av <BrowserRoute r/> , the &l T; Rute / > komponent c ould si mply koble til butikken:

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

hvis det er noe du er interessert i å bygge eller bruke, gi meg beskjed i kommentarene! For å lære mer om forskjellige rutekonfigurasjonsstrategier, sjekk ut denne introduksjonen På React Router nettsted.

Konklusjon

jeg håper denne utforskningen har bidratt til å utdype din kunnskap om klientside-ruting og har vist deg hvor enkelt Det er å oppnå Det Redux-måten.

hvis du leter etter en komplett Redux-rutingsløsning, kan du bruke redux-first-routing – pakken med en kompatibel ruter som er oppført i readme. Og hvis du finner deg selv trenger å utvikle en skreddersydd løsning, forhåpentligvis dette innlegget har gitt deg et godt utgangspunkt for å gjøre det.

hvis du vil lære mer om klientsiden ruting I React og Redux, sjekk ut følgende artikler-de var medvirkende til å hjelpe meg bedre å forstå emnene jeg dekket her:

  • La NETTADRESSEN Snakke Av Tyler Thompson
  • Du Trenger Kanskje Ikke React Router av Konstantin Tarkus
  • Trenger Jeg Selv Et Rutebibliotek? Av James K. Nelson
  • og utallige informative diskusjoner i react-router-redux utgaver.

Client-side routing er et rom med uendelige designmuligheter, og jeg er sikker på at noen av dere har spilt med ideer som ligner på de jeg har delt her. Hvis du ønsker å fortsette samtalen, jeg vil gjerne få kontakt med deg i kommentarfeltet eller Via Twitter. Takk for at du leser!

Rediger 22/06/17: Sjekk også ut denne artikkelen på redux-first-router, et eget prosjekt som bruker intelligente handlingstyper for å oppnå kraftige ruting evner.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.