Od czasu kiedy napisałemwpis wprowadzający do biblioteki react-router minęło już trochę czasu. Na tyle dużo, że w tak zwanym “międzyczasie” jej autorzy zdążyli wypuścić nową wersję. Niestety zawiera ona sporo “łamiących zmian” co powoduje, że tamten wpis stracił na aktualności. Dlatego też dziś przedstawię podstawy react-router w wersji 4, tak aby pozostać zawsze na czasie!

Wstęp

Na wstępie napiszę, że przykład prezentowany w dzisiejszych rozważaniach oparłem na prostym starterze React dostarczonym przez samego Facebooka czyli create-react-app. Poza tym na końcu wpisu znajdziesz link do repozytorium GitHuba, w którym znajduje się omawiany dzisiaj kod. Będziesz więc mógł samodzielnie go sobie “poklikać”.

Tworząc aplikację webową, do pracy potrzebna nam będzie webowa wersja react-router. Aby jej użyć, musimy zainstalować trochę inny pakiet npm niż robiliśmy to dotychczas:

yarn add react-router-dom

lub jeśli wolisz używać starego dobrego npm, to wówczas:

npm install --save react-router-dom

Podstawowa konfiguracja

Nowa wersja omawianej biblioteki została zmieniona w sposób, który powoduje, że za jej pomocą można budować “routing” w sposób bardziej deklaratywny. Odbywa się to bowiem w komponentach aplikacji.

Załóżmy, że posiadamy dwa komponenty główne: Home.js który miałby być przypisany do ścieżki / oraz About.js wyświetlany przez ścieżkę /about. Poniżej kod komponentu Home.js:

import React from 'react';

const Home = () => {
  return (
    <div className="container">
      <h1>Home!!</h1>
    </div>
  );
}

export default Home;

A tutaj kod komponentu About.js:

import React from 'react';

const About = () => {
  return (
    <div className="container">
      <h1>About!!</h1>
    </div>
  );
}

export default About;

Jak widzisz, są to zwykłe komponenty prezentacyjne, które różnią się tylko tekstem wyświetlanym na ekranie. Na potrzeby wpisu będą one dla nas wystarczające.

Renderowanie aplikacji

Przypomnijmy sobie teraz jak wyglądała konfiguracja “routingu” w starszych wersjach biblioteki react-router (kod bezpośrednio z przykładu z mojego starego wpisu na ten temat):

import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, browserHistory } from 'react-router'
import App from './components/Main';

ReactDOM.render((
  <Router history={browserHistory}>
    <Route path="/" component={App} />
  </Router>
), document.getElementById('app'));

Definicja “routingu” w tamtym przypadku odbywała się poprzez wyrenderowanie komponentu Router, wewnątrz którego, za pomocą komponentów Route definiowaliśmy poszczególne ścieżki i wiązaliśmy je z komponentami.

Teraz ciężar definiowania ścieżek został przeniesiony bezpośrednio do zainteresowanych komponentów. Załóżmy, że posiadamy główny komponent całej aplikacji o nazwie App.js. Komponent ten renderujemy zupełnie standardowo (zwyczajowo w pliku index.js):

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

Definicja routingu

W tym momencie nie dokonujemy już definicji “routingu” w momencie renderowania głównego komponentu aplikacji. Wszystko przenosi się do komponentu App.js:

import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom';

import Home from './Home';
import About from './About';

class App extends Component {
  render() {
    return (
      <Router>
        <div className="container">
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
        </div>
      </Router>
    );
  }
}

export default App;

Importy

Na początek zwróć uwagę na importy z pakietu react-router-dom. Pierwszy z nich to BrowserRouter, który od razu przemianowujemy na, po prostu, Router. Jest to główny komponent, w którym umieszczamy całą definicję naszego “routingu”.

Kolejny element to Route, który pozwala na zdefiniowanie poszczególnych ścieżek i powiązanie ich z komponentami. O tym jeszcze trochę za chwilę.

Importujemy też obiekt Link ale jeszcze go nie wykorzystujemy. O tym też opowiem za moment.

Metoda render

Przejdźmy teraz do metody render komponentu App.js. Jak widzisz, zwraca ona komponent BrowserRouter (przemianowany przy imporcie na Router). W odróżnieniu od starej wersji biblioteki, nie przekazujemy już do niego obiektu historii. Jest on bowiem pre-definiowany przez komponent BrowserRouter. Po zajrzeniu do jego implementacji łatwo stwierdzić jak można ominąć to zachowanie i zmienić domyślny obiekt historii (można zaimportować obiekt Router i przekazać mu historię w stary sposób).

Komponent BrowserRouter zawiera element div i dopiero w nim znajdujemy definicje poszczególnych ścieżek. Dlaczego tak? Otóż działa to w ten sposób: React renderuje komponent App.js, umieszcza w drzewie DOM element div, a w nim wyświetla odpowiedni komponent(y) w zależności od aktualnej ścieżki. Czyli jeśli będzie to / to w tym miejscu pokaże się komponent Home.js, a jeśli /about to komponent About.js.

Zwróć też uwagę na atrybut exact komponentu Route użyty w pierwszej ze ścieżek. Najlepiej będzie jeśli sam przetestujesz co się stanie jeśli by go zabrakło… Jeśli jednak jesteś leniwy to wyjaśniam - wymusza on dokładne porównanie ścieżek, które nie jest włączone domyślnie. Jeśli by tego atrybutu zabrakło, wówczas dla ścieżki /about obie definicje ścieżek są spełnione (/ zawiera się w /about) i wyświetlone zostałyby oba komponenty. Warto o tym pamiętać.

Layout czyli kod wspólny dla każdej z podstron

Takie podejście do “routingu” daje nam pewne możliwości, które wcześniej także były do osiągnięcia ale w trochę mniej wygodny sposób…

Komponent App.js jest bowiem w tym momencie “placeholderem” na kod wyświetlany zależnie od aktualnej ścieżki. Wydaje się więc idealnym miejscem na umieszczenie na przykład “sidebara” lub menu strony, która jest taka sama dla każdej z podstron:

import React, { Component } from 'react';
import {
  BrowserRouter as Router,
  Route,
  Link
} from 'react-router-dom';

import Home from './Home';
import About from './About';

class App extends Component {
  render() {
    return (
      <Router>
        <div className="container">
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
          </ul>

          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
        </div>
      </Router>
    );
  }
}

export default App;

W powyższym przykładzie, do kontenera wyświetlanego przez komponent dodałem listę zawierającą linki do poszczególnych ścieżek. Lista ta jest renderowana niezależnie od aktualnej ścieżki ponieważ, odpowiedni komponent wyświetlany jest w miejscu użycia komponentu Route!

Tutaj uwaga dla tych, którzy nie czytali mojego poprzedniego tekstu o react-router: do przełączania się pomiędzy ścieżkami “routingu” służy właśnie komponent Link. Jeśli użyłbyś zwykłego elementu a, to nastąpiłoby pełne przeładowanie strony. Musiałbyś więc obsługiwać zdarzenie onClick tego elementu i wykorzystywać obiekt history do wykonania przekierowania… O obiekcie history słów kilka za moment.

Dostęp do parametrów przekazywanych w adresie

Jak w każdym systemie “routingu” często sam adres zawiera pewne specyficzne informacje. Na przykład adres: http://example.com/user/2 zawiera index użytkownika (wartość 2). W takim przypadku, ścieżkę definiuje się w taki sposób:

<Route path="user/:id" component={User}/>

Za pomocą powyższego kodu informujemy react-router, że ścieżka ta powinna zawierać parametr id (dwukropek informuje, że jest to parametr).

Później, w komponencie User do wartości id możemy dostać się poprzez this.props:

<p>User id: {this.props.match.params.id}</p>

Jak możesz zauważyć, obiekt this.props komponentu wyświetlanego w ramach “routingu” zawiera obiekt match. Posiada on kilka przydatnych właściwości, a jedną z nich jest obiekt params, który to z kolei posiada wszystkie parametry przekazane w adresie (o ile tak zdefiniowaliśmy).

Dostęp do obiektu history

Na koniec jeszcze jedno… Częstym przypadkiem użycia jest sytuacja kiedy potrzebujemy w naszym kodzie wykonać “ręczne” przekierowanie do odpowiedniej ścieżki. Robi się to dokładnie tak samo jak dotychczas:

componentDidMount() {
  this.context.history.push('/');
}

Obiekt context jest specjalnym obiektem, który wstrzykiwany jest do każdego komponentu objętego “routingiem”.

React-router w wersji 4 - podsumowanie

Nowa wersja opisywanej biblioteki react-router trochę się zmieniła ale wydaje mi się, że dla kogoś kto korzystał już ze starszych jej wersji odnalezienie się wśród tych wszystkich nowości nie będzie problemem. Dla “świeżaków” natomiast nie powinna ona stanowić większego wyzwania niż wcześniej. Wszystkich zainteresowanych zachęcam do przejrzenia dokumentacji oraz do zapoznania się z kodem dzisiejszego przykładu, jaki zamieściłem w repozytorium GitHub.

A może chciałbyś lepiej poznać react-router?

Powyższy wpis to tylko niezbędne minimum, które trzeba znać aby zacząć pracować z biblioteką react-router. Jeśli chciałbyś dogłębnie i od podstaw poznać tę bibliotekę to specjalnie dla Ciebie przygotowałem specjalne kursy on-line, dzięki którym od podstaw nauczysz się react-routera, ale też Reacta oraz Reduxa! Kliknij czerwony guzik w “boksie” poniżej aby dowiedzieć się więcej: