Utjämning med React: React Router

denna handledning är den första i en tredelad serie om React av Brad Westfall. När Brad slog mig detta, han påpekade att det finns en bra mängd tutorials på att komma igång i React, men inte så mycket om var att gå därifrån. Om du är helt ny att reagera rekommenderar jag att du tittar på den här introvideoen. Denna serie plockar upp där grunderna lämnar.

artikelserie:

  1. React Router (du är här!)
  2. Behållarkomponenter
  3. Redux

Varning! Den här artikeln skrevs pre-React Router 4, som har blivit ett mer standardval för routing i React. Det finns en ny artikel som täcker React Router 4 här bör du definitivt läsa.

när jag först lärde mig hittade jag massor av nybörjarguider (dvs 1, 2, 3, 4) som visade hur man gör enskilda komponenter och gör dem till DOM. De gjorde ett bra jobb med att lära sig grunderna som JSX och rekvisita, men jag kämpade med att räkna ut hur React fungerar i den större bilden – som en verklig enda sida ansökan (SPA). Eftersom denna serie täcker mycket material, kommer det inte att täcka de absoluta nybörjarkoncepten. Istället börjar det med antagandet att du redan förstår hur man skapar och återger minst en komponent.

för vad det är värt, här är några andra bra guider som syftar till nybörjare:

  • reagera.js och hur passar det in med allt annat?
  • Ompröva (Bransch) Bästa Praxis
  • Reagera.JS introduktion för människor som vet precis tillräckligt jQuery att komma med

Seriekod

denna serie kommer också med lite kod att spela med på GitHub. Under hela serien kommer vi att bygga ett grundläggande SPA fokuserat kring användare och widgets.

för att hålla sakerna enkla och korta, kommer exemplen i denna serie att börja med att anta att React och React Router hämtas från en CDN. Så du kommer inte att se require() eller import i de omedelbara exemplen nedan. Mot slutet av denna handledning kommer vi dock att introducera Webpack och Babel för GitHub-guiderna. Vid den tiden är det allt ES6!

React-Router

React är inte ett ramverk, det är ett bibliotek. Därför löser det inte alla applikationens behov. Det gör ett bra jobb med att skapa komponenter och tillhandahålla ett system för att hantera staten, men att skapa ett mer komplext SPA kommer att kräva en stödjande roll. Den första som vi ska titta på är React Router.

om du har använt någon front-end-router tidigare kommer många av dessa begrepp att vara bekanta. Men till skillnad från någon annan router jag har använt tidigare använder React Router JSX, vilket kan se lite konstigt ut först.

som en primer är det så här det är att göra en enda komponent:

var Home = React.createClass({ render: function() { return (<h1>Welcome to the Home Page</h1>); }});ReactDOM.render(( <Home />), document.getElementById('root'));

så här skulle komponenten Home återges med React Router:

...ReactDOM.render(( <Router> <Route path="/" component={Home} /> </Router>), document.getElementById('root'));

Observera att <Router> och <Route> är två olika saker. De är tekniskt reagera komponenter, men de faktiskt inte skapa DOM själva. Även om det kan se ut som <Router> själv återges till 'root', definierar vi faktiskt bara regler om hur vår applikation fungerar. Framåt ser du detta koncept ofta: komponenter finns ibland inte för att skapa DOM själva, utan för att samordna andra komponenter som gör det.

i exemplet definierar <Route> en regel där besök på hemsidan / kommer att göra Home – komponenten till 'root'.

flera rutter

i föregående exempel är den enda rutten mycket enkel. Det ger oss inte mycket värde eftersom vi redan hade förmågan att göra Home – komponenten utan att routern var inblandad.

React routers ström kommer in när vi använder flera rutter för att definiera vilken komponent som ska återges baserat på vilken sökväg som för närvarande är aktiv:

ReactDOM.render(( <Router> <Route path="/" component={Home} /> <Route path="/users" component={Users} /> <Route path="/widgets" component={Widgets} /> </Router>), document.getElementById('root'));

varje <Route> återger sin respektive komponent när dess sökväg matchar webbadressen. Endast en av dessa tre komponenter kommer att återges i 'root' vid varje given tidpunkt. Med denna strategi monterar vi routern till DOM 'root' en gång, sedan byter routern komponenter in och ut med ruttändringar.

det är också värt att notera att routern kommer att byta rutter utan att göra förfrågningar till servern, så föreställ dig att varje komponent kan vara en helt ny sida.

återanvändbar Layout

vi börjar se den ödmjuka början på en enda Sidapplikation. Men det löser fortfarande inte verkliga problem. Visst kan vi bygga de tre komponenterna för att vara fullständiga HTML-sidor, men hur är det med återanvändning av kod? Chansen är stor att dessa tre komponenter delar gemensamma tillgångar som en rubrik och sidofält, så hur förhindrar vi HTML-upprepning i varje komponent?

låt oss föreställa oss att vi byggde en webbapp som liknade denna mockup:

en enkel webbplats mockup.

när du börjar tänka på hur denna mockup kan delas upp i återanvändbara sektioner kan du sluta med den här tanken:

hur du kan bryta upp den enkla webbmockupen i sektioner.

tänkande när det gäller kapslade komponenter och layouter gör att vi kan skapa återanvändbara delar.

plötsligt låter konstavdelningen dig veta att appen behöver en sida för att söka widgets som liknar sidan för att söka användare. Med Användarlista och Widgetlista som båda kräver samma” look ” för sin söksida, är tanken att ha söklayout som en separat komponent ännu mer meningsfull nu:

Sök efter widgets nu, i stället för användare, men de överordnade sektionerna förblir desamma.

söklayout kan vara en överordnad mall för alla typer av söksidor nu. Och medan vissa sidor kan behöva söklayout, andra kan direkt använda huvudlayout utan det:

en layout frikopplad.

Detta är en vanlig strategi och om du har använt något mallsystem har du förmodligen gjort något mycket liknande. Låt oss nu arbeta med HTML. Till att börja med gör vi statisk HTML utan att överväga JavaScript:

<div> <!-- Main Layout --> <div class="app"> <header class="primary-header"><header> <aside class="primary-aside"></aside> <main> <!-- Search Layout --> <div class="search"> <header class="search-header"></header> <div class="results"> <!-- User List --> <ul class="user-list"> <li>Dan</li> <li>Ryan</li> <li>Michael</li> </ul> </div> <div class="search-footer pagination"></div> </div> </main> </div></div>

kom ihåg att 'root' – elementet alltid kommer att finnas eftersom det är det enda elementet som den ursprungliga HTML-kroppen har innan JavaScript startar. Ordet” root ” är lämpligt eftersom hela vår React-applikation kommer att monteras på den. Men det finns inget ”rätt namn” eller konvention till vad du kallar det. Jag har valt ”root”, så vi fortsätter att använda den genom exemplen. Se bara till att montering direkt på <body> – elementet är mycket avskräckt.

när du har skapat den statiska HTML, konvertera den till React-komponenter:

var MainLayout = React.createClass({ render: function() { // Note the `className` rather than `class` // `class` is a reserved word in JavaScript, so JSX uses `className` // Ultimately, it will render with a `class` in the DOM return ( <div className="app"> <header className="primary-header"><header> <aside className="primary-aside"></aside> <main> {this.props.children} </main> </div> ); }});var SearchLayout = React.createClass({ render: function() { return ( <div className="search"> <header className="search-header"></header> <div className="results"> {this.props.children} </div> <div className="search-footer pagination"></div> </div> ); }});var UserList = React.createClass({ render: function() { return ( <ul className="user-list"> <li>Dan</li> <li>Ryan</li> <li>Michael</li> </ul> ); }});

bli inte för distraherad mellan vad jag kallar ”Layout” vs ”komponent”. Alla tre av dessa är React-komponenter. Jag väljer bara att kalla två av dem” layouter ” eftersom det är den roll de utför.

vi kommer så småningom att använda ”kapslade rutter” för att placera UserList inuti SearchLayout, sedan inuti MainLayout. Men först märker du att när UserList placeras inuti sin förälder SearchLayout, kommer föräldern att använda this.props.children för att bestämma dess plats. Alla komponenter har this.props.children som en prop, men det är bara när komponenter är kapslade att den överordnade komponenten får denna prop fylls automatiskt av React. För komponenter som inte är överordnade komponenter är this.props.children null.

kapslade rutter

så hur får vi dessa komponenter att bo? Routern gör det för oss när vi häckar rutter:

ReactDOM.render(( <Router> <Route component={MainLayout}> <Route component={SearchLayout}> <Route path="users" component={UserList} /> </Route> </Route> </Router>), document.getElementById('root'));

komponenter kommer att kapslas i enlighet med hur routern häckar sina rutter. När användaren besöker rutten /users kommer React Router att placera komponenten UserList inuti SearchLayout och sedan båda inuti MainLayout. Slutresultatet av att besöka /users kommer att vara de tre kapslade komponenterna placerade inuti 'root'.

Lägg märke till att vi inte har någon regel för när användaren besöker startsidan sökvägen (/) eller vill söka widgets. De lämnades för enkelhet, men låt oss sätta in dem med den nya routern:

ReactDOM.render(( <Router> <Route component={MainLayout}> <Route path="/" component={Home} /> <Route component={SearchLayout}> <Route path="users" component={UserList} /> <Route path="widgets" component={WidgetList} /> </Route> </Route> </Router>), document.getElementById('root'));

du har förmodligen märkt nu att JSX följer XML-regler i den meningen att komponenten Route antingen kan skrivas som en tagg: <Route /> eller två: <Route>...</Route>. Detta gäller för alla JSX inklusive dina anpassade komponenter och normala DOM-noder. Till exempel är <div /> giltig JSX och konverterar till <div></div> när den återges.

för korthet, Tänk dig att WidgetList liknar UserList.

eftersom <Route component={SearchLayout}> har två barnvägar nu kan användaren besöka /users eller /widgets och motsvarande <Route> laddar sina respektive komponenter inuti SearchLayout – komponenten.

lägg också märke till hur Home — komponenten kommer att placeras direkt inuti MainLayout utan att SearchLayout är involverad-på grund av hur <Route>s är kapslade. Du kan förmodligen föreställa dig att det är lätt att ordna om hur layouter och komponenter är kapslade genom att ordna om rutterna.

IndexRoutes

React Router är mycket uttrycksfull och ofta finns det mer än ett sätt att göra samma sak. Till exempel kunde vi också ha skrivit ovanstående router så här:

ReactDOM.render(( <Router> <Route path="/" component={MainLayout}> <IndexRoute component={Home} /> <Route component={SearchLayout}> <Route path="users" component={UserList} /> <Route path="widgets" component={WidgetList} /> </Route> </Route> </Router>), document.getElementById('root'));

trots sitt olika utseende fungerar de båda på exakt samma sätt.

valfria Ruttattribut

ibland kommer <Route> att ha ETT component – attribut utan path, som i SearchLayout – rutten ovanifrån. Andra gånger kan det vara nödvändigt att ha en <Route> med en path och ingen component. För att se varför, låt oss börja med det här exemplet:

<Route path="product/settings" component={ProductSettings} /><Route path="product/inventory" component={ProductInventory} /><Route path="product/orders" component={ProductOrders} />

/product – delen av path är repetitiv. Vi kan ta bort repetitionen genom att linda in alla tre rutterna i en ny <Route>:

<Route path="product"> <Route path="settings" component={ProductSettings} /> <Route path="inventory" component={ProductInventory} /> <Route path="orders" component={ProductOrders} /></Route>

återigen visar React Router sin uttrycksfullhet. Frågesport: märkte du problemet med båda lösningarna? För tillfället har vi inga regler för när användaren besöker /product sökvägen.

för att åtgärda detta kan vi lägga till en IndexRoute:

<Route path="product"> <IndexRoute component={ProductProfile} /> <Route path="settings" component={ProductSettings} /> <Route path="inventory" component={ProductInventory} /> <Route path="orders" component={ProductOrders} /></Route>

använd <Link> inte <a>

när du skapar ankare för dina rutter måste du använda <Link to="">istället för <a href="">. Oroa dig dock inte, när du använder komponenten <Link> kommer React Router i slutändan att ge dig ett vanligt ankare i DOM. Att använda <Link> är dock nödvändigt för React Router för att göra en del av dess routing Magi.

Låt oss lägga till några länkar (ankare) till vår MainLayout:

var MainLayout = React.createClass({ render: function() { return ( <div className="app"> <header className="primary-header"></header> <aside className="primary-aside"> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/users">Users</Link></li> <li><Link to="/widgets">Widgets</Link></li> </ul> </aside> <main> {this.props.children} </main> </div> ); }});

attribut på <Link> komponenter kommer att skickas till ankaret de skapar. Så här JSX:

<Link to="/users" className="users">

kommer att bli detta i DOM:

<a href="/users" class="users">

om du behöver skapa ett ankare för icke-routervägar, till exempel en extern webbplats, använd sedan vanliga ankartaggar som vanligt. För mer information, se dokumentationen för IndexRoute och länk.

aktiva länkar

en cool funktion i komponenten <Link> är dess förmåga att veta när den är aktiv:

<Link to="/users" activeClassName="active">Users</Link>

om användaren är på /users sökvägen kommer routern att söka efter matchande ankare som gjordes med <Link> och det kommer att växla deras active klass. Se mer om den här funktionen.

webbläsarhistorik

för att förhindra förvirring har jag utelämnat en viktig detalj tills nu. <Router> behöver veta vilken historikspårningsstrategi som ska användas. React Router docs rekommenderar browserHistory som implementeras enligt följande:

var browserHistory = ReactRouter.browserHistory;ReactDOM.render(( <Router history={browserHistory}> ... </Router>), document.getElementById('root'));

i tidigare versioner av React Router krävdes inte attributet history och standardvärdet var att använda hashHistory. Som namnet antyder använde det ett # hash-tecken i webbadressen för att hantera front-end SPA-stil routing, liknande vad du kan förvänta dig av en ryggrad.JS router.

med hashHistory kommer webbadresser att se ut så här:

  • example.com
  • example.com/#/users?_k=ckuvup
  • example.com/#/widgets?_k=ckuvup

Vad är det med de fula frågesträngarna men?

när browserHistory implementeras ser banorna mer organiska ut:

  • example.com
  • example.com/users
  • exempel.com / widgets

det finns en varning men på servern när browserHistory används på front-end. Om användaren startar sitt besök på example.com och sedan navigerar till /users och /widgets, hanterar React Router detta scenario som förväntat. Men om användaren startar sitt besök genom att skriva example.com/widgets direkt i webbläsaren, eller om de uppdaterar på example.com/widgets, måste webbläsaren göra minst en begäran till servern för /widgets. Om det inte finns en server-side router men, detta kommer att leverera en 404:

var försiktig med webbadresser. Du behöver en server side router.

för att lösa 404-problemet från servern rekommenderar React Router en jokerteckenrouter på serversidan. Med denna strategi, oavsett vilken server-sida rutt kallas, servern ska alltid tjäna samma HTML-fil. Om användaren startar direkt vid example.com/widgets, trots att samma HTML-fil returneras, är React Router smart nog att ladda rätt komponent.

användaren kommer inte att märka något konstigt, men du kan ha oro över att alltid betjäna samma HTML-fil. I kodexempel fortsätter denna serie att använda” wildcard router ” -strategin, men det är upp till dig att hantera din routing på serversidan på sätt som du tycker är lämpliga.

kan React Router användas på både server-och klientsidan på ett isomorfiskt sätt? Visst kan det, men det är långt bortom omfattningen av denna handledning.

omdirigera med browserHistory

browserHistory objektet är en singleton så att du kan inkludera det i någon av dina filer. Om du behöver omdirigera användaren manuellt i någon av din kod kan du använda den push – metoden för att göra det:

browserHistory.push('/some/path');

Ruttmatchning

React router hanterar ruttmatchning på samma sätt som andra routrar:

<Route path="users/:userId" component={UserProfile} />

den här rutten matchar när användaren besöker en sökväg som börjar med users/ och har något värde efteråt. Det kommer att matcha /users/1, /users/143 eller till och med /users/abc (som du måste validera på egen hand).

React Router skickar värdet för :userId som en prop till UserProfile. Dessa rekvisita nås som this.props.params.userId inuti UserProfile.

Router Demo

vid denna tidpunkt har vi tillräckligt med kod för att visa en demo.

se Pen React-Router Demo av Brad Westfall (@bradwestfall) på CodePen.

om du klickade på några rutter i exemplet kanske du märker att webbläsarens bakåt-och framåtknappar fungerar med routern. Detta är en av de främsta anledningarna till att dessa history – strategier finns. Tänk också på att med varje rutt du besöker finns det inga förfrågningar till servern förutom den allra första som får den ursprungliga HTML. Hur coolt är det?

ES6

i vårt CodePen-exempel är React, ReactDOM och ReactRouter globala variabler från ett CDN. Inuti objektet ReactRouter finns alla slags saker vi behöver som komponenterna Router och Route. Så vi kunde använda ReactRouter så här:

ReactDOM.render(( <ReactRouter.Router> <ReactRouter.Route ... /> </ReactRouter.Router>), document.getElementById('root'));

här måste vi prefixa alla våra routerkomponenter med deras överordnade objekt ReactRouter. Eller vi kan använda ES6: s nya förstörande syntax så här:

var { Router, Route, IndexRoute, Link } = ReactRouter

detta ”extraherar” delar av ReactRouter till normala variabler så att vi kan komma åt dem direkt.

från och med nu kommer exemplen i denna serie att använda en mängd olika ES6-syntaxer inklusive destruktion, spridningsoperatören, import/export och kanske andra. Det kommer att finnas en kort förklaring av varje ny syntax som de visas och GitHub repo som kommer med denna serie har också massor av ES6 förklaringar.

buntning med webpack och Babel

som tidigare nämnts kommer denna serie med en GitHub repo så att du kan experimentera med kod. Eftersom det kommer att likna ett verkligt SPA, kommer det att använda verktyg som webpack och Babel.

  • webpack buntar flera JavaScript-filer i en fil för webbläsaren.
  • Babel konverterar ES6 (ES2015) kod till ES5, eftersom de flesta webbläsare ännu inte förstår alla ES6. Eftersom denna artikel åldrar, webbläsare kommer att stödja ES6 och Babel kanske inte behövs.

om du inte är för bekväm med att använda dessa verktyg ännu, oroa dig inte, exempelkoden har allt setup redan så att du kan fokusera på React. Men var noga med att granska exempelkodens README.md fil för ytterligare arbetsflödesdokumentation.

var försiktig med föråldrad Syntax

att söka på Google för information om React Router kan landa dig på en av de många artiklarna eller StackOverflow-sidorna som skrevs när React Router var i sin pre-1.0-release. Många funktioner från pre-1.0-utgåvan är föråldrad nu. Här är en kort lista:

  • <Route name="" /> är föråldrat. Använd <Route path="" /> istället.
  • <Route handler="" /> är föråldrat. Använd <Route component="" /> istället.
  • <NotFoundRoute /> är föråldrat. Se alternativ
  • <RouteHandler /> är föråldrat.
  • willTransitionTo är föråldrat. Se onEnter
  • willTransitionFrom är föråldrad. Se onLeave
  • ” platser ”kallas nu”historier”.

se hela listan för 1.0.0 och 2.0.0

sammanfattning

det finns fortfarande fler funktioner i React Router som inte visades, så se till att kolla in API-dokumenten. Skaparna av React Router har också skapat en steg-för-steg-handledning för React Router och även kassan denna React.js Conf Video om hur reagera Router skapades.

särskilt tack till Lynn Fisher för konstverket @ lynnandtonic

artikelserie:

  1. React Router (du är här!)
  2. Behållarkomponenter
  3. Redux

Lämna ett svar

Din e-postadress kommer inte publiceras.