Norėdami savo aplikacijoje turėti daugiau nei vieną puslapį turėsime
implementuoti router'į. Kad nereiktų to daryti patiems nuo nulio mums
padės su React suderinamos route'inimo bibliotekos react-router ir
react-router-dom. Pastaroji reikalinga tam, kad react router galetų
valdyti naršyklės history ir puslapio adresą įgalintant aplikacijos
navigaciją tarp skirtingų puslapių kaip įprastoje svetainėje.
npm install react-router
npm install react-router-domImportuojame BrowserRouter komponentą kuris leis router'iui valdyti naršyklės history bei puslapio adresą
src/root/App.jsx
import { BrowserRouter as Router } from 'react-router-dom';Apgaubiame mūsų App komponento elementus Router komponentu render funkcijoje
src/root/App.jsx
render() {
return (
<Router>
<LandingLayout>
<LandingPage />
</LandingLayout>
</Router>
);
}Route iš react-router-dom tai dar vienas komponentas kuris priima du
props: path ir component. path prop'se nurodome kokiam url atitikus
šis komponentas bus iš'render'intas, o kartu su juo ir mūsų komponentas kurį
nurodome per component prop'są.
Paverčiame mūsų LandingPage puslapio komponentą į Route komponentą
src/root/App.jsx
render() {
return (
<Router>
<LandingLayout>
<Route path="/" component={LandingPage} />
</LandingLayout>
</Router>
);
}Pabandykime nueiti į kokį nors kitą puslapį, pvz: /test - kaip matome
vistiek yra rodomas mūsų landing page. Taip atsitinka todėl, kad Router
komponento path prop'se mūsų nurodytas url yra tikrinamas taip, kad užtenka
jog mūsų įrašyta reikšmė egzistuotų pačiame url, taigi šiuo atveju mūsų
nurodytas path / egzistuos visuose url kokius tik bandysime atidaryti.
Kad mūsų Route komponentas būtų renderinamas tik tada kai url tiksliai
atitinka nurodytą path nustatome exact prop'są.
src/root/App.jsx
<Route exact path="/" component={LandingPage} />Pabandykime nueiti į /test url ir pamatysime kad mūsų landing page
komponentas nebėra išrenderinamas.
Nuėję į puslapio url koris nėra užregistruotas nei viename Route komponente
nematysime nieko išrenderinamo. Kad vartotojui parodyti žinutę jog jo ieškomas
puslapis nerastas, susikurkime 404 puslapį.
Pirmiausia susikurkime pačio puslapio komponentą
src/pages/NotFound/NotFoundPage.jsx
import * as React from 'react';
import styles from './NotFoundPage.scss';
export class NotFoundPage extends React.Component {
render() {
return (
<section className="section">
<h1 className={`title ${styles.isSuper}`}>404</h1>
<p className={`${styles.isHighlighted}`}>Sorry, the thing you`re looking for was not found.</p>
</section>
);
}
}Pridėkime šiek tiek stiliaus
src/pages/NotFound/NotFoundPage.jsx
.isSuper {
color: white !important;
text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
font-size: 6rem !important;
}
.isHighlighted {
background-color: #00d1b2;
padding: 3px 8px;
display: inline-block;
color: white !important;
}Pridedame naują Route į App komponentą ir norodome mūsų naują NotFound
komponentą kaip Route komponento component propso reikšmę.
Šį kartą nenustatome Route komponentui jokios path prop'so reikšmės,
kadangi norime jog visi url kurie nėra nurodyti atsidurtų šiame komponente.
src/root/App.jsx
render() {
return (
<Router>
<LandingLayout>
<Route exact path="/" component={LandingPage} />
<Route component={NotFoundPage} />
</LandingLayout>
</Router>
);
}Jeigu vėl pabandysime atsidaryti /test puslapį kuris neegzistuoja
matysime mūsų naują 404 puslapį. Puiku! Ne visai... Nueikime į /
puslapį. Matome kad išsirenderina ne tik mūsų LandingPage komponentas
tačiau kartu ir mūsų naujasis NotFound komponentas. Taip atsitinka todėl
kad routeris visus Route renderina "inclusive" būdu. T.y. visus Route
kurių path tinka dabar atidarytam puslapiui. Tam kad priverstume routerį
renderinti puslapius "exclusive" būdu panaudosime dar vieną komponentą iš
react-router-dom bibliotekos - Switch. Visi Route gyvenantys Switch
komponento viduje bus renderinami exclusive būdu. T.y. routeriui suradus
pirmąjį Route kurio path tinka mūsų dabartiniam puslapiui - jis bus
išrenderintas, o visi likę Route (net ir tie kurie irgi tiktų) - ne.
Importuokime Switch komponentą ir apgaubkime juo visus Route komponentus
src/root/App.jsx
import { Switch } from 'react-router-dom'; render() {
return (
<Router>
<LandingLayout>
<switch>
<Route exact path="/" component={LandingPage} />
<Route component={NotFoundPage} />
</switch>
</LandingLayout>
</Router>
);
}Reiktų nepamirši atkreipti dėmesio į Route komponentų esančių po Switch komponentu eiliškumą, kadangi kaip ir minėjome Switch pasirinks ir renderins pirmą tikusį Route.
Kad patektume į kitus puslapius savo aplikacijoje mums reikės nuorodų.
Tradiciškai nuorodoms naudojame <a> elementus, tačiau su React router
nudosime Link komponentą. Link komponentas mums leis naviguoti tarp
aplikacijos puslapių neperkraunant pačios aplikacijos.
Panaudokime Link komponentą mūsų NotFound puslapyje
src/pages/NotFound/NotFoundPage.jsx
import { Link } from 'react-router-dom'; render() {
return (
<section className="section">
<h1 className={`title ${styles.isSuper}`}>404</h1>
<p className={`${styles.isHighlighted}`}>Sorry, the thing you`re looking for was not found.</p>
<hr />
<p>
You can try returning <Link to="/">home</Link> to continue your search for glory.
</p>
</section>
);
}Šiuo metu visi mūsų aplikacijos puslapiai apgaubdi LandingLlayout
komponentu. Susikurtkime savo Route komponentą kuris veiks kaip
wrapper'is react-router-dom bibliotekos Route komponentui ir leis
mums nurodyti layout komponentą išskirtinai kiekvienam individualiam Route.
src/root/Route.jsx
import * as React from 'react';
import { Route as ReactRoute } from 'react-router-dom';
export class Route extends React.Component {
render() {
const { component, layout, ...rest } = this.props;
let routeComponent = props => React.createElement(component, props);
if (layout) {
routeComponent = props =>
React.createElement(layout, props, React.createElement(component, props));
}
return <ReactRoute {...rest} render={routeComponent} />;
}
}Pakeiskime mūsų Route komponentus naujuoju Route wrapper'iu
src/root/App.jsx
import { Route } from './Route';Taip pat iškelkime mūsų layout komponentą ir nurodykime jį kaip layout
prop reikšmę kiekvienam mūsų naujam Route komponentui.
src/root/App.jsx
render() {
return (
<Router>
<Switch>
<Route exact path="/" component={LandingPage} layout={LandingLayout} />
<Route component={NotFoundPage} layout={LandingLayout} />
</Switch>
</Router>
);
}Kadangi jau turime galimybę nurodyti layout komponentą kiekvienam individualiam Route susikurkime dar vieną layout komponentą ir panaudokime jį mūsų 404 puslapiui
src/layouts/Clean/CleanLayout.jsx
import * as React from 'react';
import { Link } from 'react-router-dom';
import styles from './CleanLayout.scss';
export class CleanLayout extends React.Component {
render() {
return (
<React.Fragment>
<nav className="navbar is-primary is-fixed-top">
<div className="container">
<div className="navbar-brand">
<Link className="navbar-item" to="/">
<img src="https://placehold.it/112x28?text=Logo" alt="Useful-useless" />
</Link>
<a role="button" className={`navbar-burger ${styles.burger}`}>
<span aria-hidden="true" />
<span aria-hidden="true" />
<span aria-hidden="true" />
</a>
</div>
<div className="navbar-menu">
<div className="navbar-start">
<Link className="navbar-item" to="/">
Home
</Link>
</div>
</div>
</div>
</nav>
<section className="section">
<div className="container">{this.props.children}</div>
</section>
</React.Fragment>
);
}
}Šiek tiek stiliaus
src/layouts/Clean/CleanLayout.scss
.burger {
color: white;
background: none;
border: none;
}Panaudokime mūsų nująjį layout 404 Route komponentui
src/root/App.jsx
<Route component={NotFoundPage} layout={CleanLayout} />Funckija iškviečiama iš karto kai React komponentas yra pirmą kartą įkeliamas
į DOM. Šiame metode galime atlikti įvairias inicializacijas, arba atlikti
DOM manipuliacijas (pvz. pridėti klasę ant <body> elemento).
Ši funkcija iškviečiama kai komponento props (arba state - apie tai sužinosime vėliau) atsinaujina. Tai puiki vieta atlikti props modifikacijoms (pvz. vieno iš props datos formatavimas) arba papildomoms DOM manipuliacijoms.
Funkcija iškviečiama prieš komponentą išimant iš DOM. Tai gera vieta nutraukti intervalus ar išvalyti kitus resursus iš atminties.
Šioje funkcijoje galime nuspręsti ar komponentas turėtų atsinaujinti. Dažniausiai ši funkcija nėra naudojama, tačiau speceliais scenarijais kai pvz. tam tikro prop pasikeitimas sukelia per daug re-render'ių galime jo reikšmę patikrinti ir atitinkamai atiduoti boolean value kuris ir nuspręs ar komponentas bus atnaujintas.
src/components/Item/Item.jsx
import * as React from 'react';
import styles from './Item.scss';
import './Cover.scss';
export class Item extends React.Component {
componentDidMount() {
WindowTools.setBodyImage(this.props.item.image);
}
componentWillUnmount() {
WindowTools.setBodyImage(null);
}
render() {
const {
item: { title, subtitle, description },
} = this.props;
return (
<div>
<div className={styles.imageSpacer} />
<h1 className={`title ${styles.isSuper}`}>{title}</h1>
<p className={`subtitle ${styles.isHighlighted}`}>{subtitle}</p>
<div className={`card ${styles.spacedBottom}`}>
<div className="card-content">
<div className="content">
<div>{description}</div>
</div>
</div>
</div>
</div>
);
}
}src/components/Item/Item.scss
.imageSpacer {
height: 70vh;
}
.isSuper {
color: white !important;
text-shadow: 0 2px 18px rgba(0, 0, 0, 0.5);
font-size: 6rem !important;
}
.isHighlighted {
background-color: #00d1b2;
padding: 3px 8px;
display: inline-block;
color: white !important;
}
.spacedBottom {
margin-bottom: 2rem;
}src/components/Item/Cover.scss
html {
height: 100%;
}
body {
min-height: 100%;
padding-top: 3.25rem;
background-size: cover;
background-attachment: fixed;
background-repeat: no-repeat;
background-position-y: 3.25rem;
background-position-x: center;
}Susikurkime trūkstamą WindowTools helper klasę
src/services/Utils/WindowTools.js
export class WindowTools {
static setBodyImage(image) {
document.body.style.backgroundImage = image ? `url("${image}")` : null;
}
}Susikurkime puslapį kuriame atvaizduosime mūsų naująjį Item komponentą
src/pages/Item/ItemPage.jsx
import * as React from 'react';
import { Item } from '../../components/Item/Item';
export class ItemPage extends React.Component {
constructor(props) {
super(props);
this.mockItem = {
id: 1,
title: 'test',
image: 'https://www.supercars.net/blog/wp-content/uploads/2016/12/Ferrari-LaFerrari.jpg',
subtitle: 'Test subtitle',
description: 'Test description',
};
}
render() {
return <Item item={this.mockItem} />;
}
}Sukurtkime naują Route naujajam Item puslapiui
src/root/App.jsx
import { ItemPage } from '../pages/Item/ItemPage'; render() {
return (
<Router>
<Switch>
<Route exact path="/" component={LandingPage} layout={LandingLayout} />
<Route exact path="/item/:id" component={ItemPage} layout={CleanLayout} />
<Route component={NotFoundPage} layout={CleanLayout} />
</Switch>
</Router>
);
}Atkreipkime dėmesį į :id kurį įrašėme į Route path - tai route path
kintamasis. Kintamuosius galime pasiekti iš komponento ir pagal tai spręsti
koks įrašas bus atvaizduotas.
Pirmiausia susikurkime daugiau įrašų
src/mocks/data.json
https://raw.githubusercontent.com/nfq-react-workshop/useful-useless/workshop-3/src/mocks/data.json
Panaudokime naujuosius įrašus Landing puslapyje
src/pages/Landing/LandingPage.jsx
import mock from '../../mocks/data';
...
<ItemList items={mock.items} />Taip pat panaudokime šiuos įrašus ir mūsų Item puslapyje surasdami tinkamą
įrašą pagal route path kintamąjį kurį pasieksime per this.props.match
src/pages/Item/ItemPage.jsx
import * as React from 'react';
import { Item } from '../../components/Item/Item';
import mock from '../../mocks/data';
export class ItemPage extends React.Component {
render() {
const { match } = this.props;
const item = mock.items.find(i => i.id === match.params.id);
return <Item item={item} />;
}
}React'e DOM eventai naudojami labai panašiai kaip ir įprastame HTML.
Ant elemento rašome on* (* bet koks DOM eventas, pvz: click) ir
priskiriame funkciją. Ši funkcija bus iškviesta įvykus laukiamam veiksmui.
Pridėkime click eventą į mūsų Item komponentą:
src/components/Item/Item.jsx
onSpacerClick() {
WindowTools.invertBodyImage();
} render() {
...
return (
<div>
<div className={styles.imageSpacer} onClick={this.onSpacerClick} />
...
</div>
);
}Pridėkime šią funkciją į WindowTools helper klasę
src/services/Utils/WindowTools.js
static invertBodyImage() {
document.body.style.filter = 'grayscale(100%)';
}Į kiekvieną event handler'į gauname Event objektą. Šiame objekte galime rasti įvairių naudingų funkcijų bei properčių.
Sustabdo standartinių naršyklės funkcijų vykdymą, pvz. klausydami click
evento ant <a> elemento ir iškvietę šią funkciją galime sustabdyti
naršyklės navigaciją į href nurodytą ant <a> elemento.
Iškvietę šią funckiją galime sustabdyti eventų "bubble'inimąsi" per DOM medį.
Šiame propertyje rasime DOM elementą kuris ir iššaukė mūsų klusomą event'ą.