Routing klienta (za pomocą routera reaktywnego) i routing po stronie serwera

108

Myślałem i jestem zdezorientowany z routingiem między klientem a serwerem. Załóżmy, że używam ReactJS do renderowania po stronie serwera przed wysłaniem żądania z powrotem do przeglądarki internetowej i używam React-router jako routingu po stronie klienta do przełączania się między stronami bez odświeżania jako SPA.

Przychodzi mi na myśl:

  • Jak interpretowane są trasy? Na przykład żądanie ze strony głównej ( /home) do strony postów ( /posts)
  • Gdzie przebiega routing, po stronie serwera lub klienta?
  • Skąd wie, jak jest przetwarzany?
heartmon
źródło
1
Proponuję poczytać o History API w przeglądarkach.
WiredPrairie

Odpowiedzi:

137

Uwaga, ta odpowiedź dotyczy React Router w wersji 0.13.x - nadchodząca wersja 1.0 wygląda na to, że będzie miała znacznie inne szczegóły implementacji

serwer

To minimum w server.jsprzypadku routera React:

var express = require('express')
var React = require('react')
var Router = require('react-router')

var routes = require('./routes')

var app = express()

// ...express config...

app.use(function(req, res, next) {
  var router = Router.create({location: req.url, routes: routes})
  router.run(function(Handler, state) {
    var html = React.renderToString(<Handler/>)
    return res.render('react_page', {html: html})
  })
})

Gdzie routesmoduł eksportuje listę tras:

var React = require('react')
var {DefaultRoute, NotFoundRoute, Route} = require('react-router')

module.exports = [
  <Route path="/" handler={require('./components/App')}>
    {/* ... */}
  </Route>
]

Za każdym razem, gdy do serwera kierowane jest żądanie, tworzysz jednorazową Routerinstancję skonfigurowaną z przychodzącym adresem URL jako jego statyczną lokalizacją, która jest rozpoznawana w drzewie tras w celu skonfigurowania odpowiednich dopasowanych tras, wywołując z powrotem z najwyższym poziomem program obsługi tras, który ma zostać wyrenderowany, oraz rekord, które trasy podrzędne są dopasowane na każdym poziomie. To jest właśnie to, co jest konsultowane podczas używania <RouteHandler>komponentu w komponencie obsługi tras do renderowania trasy podrzędnej, która została dopasowana.

Jeśli użytkownik wyłączył JavaScript lub ładuje się wolno, wszystkie kliknięte przez niego linki ponownie trafią na serwer, co zostanie rozwiązane ponownie, jak powyżej.

Klient

To minimum w client.jsprzypadku react-routera (ponowne użycie tego samego modułu tras):

var React = require('react')
var Router = require('react-router')

var routes = require('./routes')

Router.run(routes, Router.HistoryLocation, function(Handler, state) {
  React.render(<Handler/>, document.body)
})

Kiedy dzwonisz Router.run(), tworzy dla Ciebie instancję routera za kulisami, która jest ponownie używana za każdym razem, gdy nawigujesz po aplikacji, ponieważ adres URL może być dynamiczny na kliencie, w przeciwieństwie do serwera, na którym pojedyncze żądanie ma stały adres URL.

W tym przypadku używamy HistoryLocation, który używa Historyinterfejsu API, aby upewnić się, że po naciśnięciu przycisku Wstecz / Dalej dzieje się właściwa rzecz. Istnieje również, HashLocationktóry zmienia adres URL, hashaby tworzyć wpisy historii i nasłuchuje window.onhashchangezdarzenia, aby wyzwolić nawigację.

Kiedy używasz <Link>komponentu react-router , dajesz mu toatrybut, który jest nazwą trasy paramsoraz innymi querydanymi, których potrzebuje trasa. <a>Świadczonych przez tego składnika ma onClickobsługi, która ostatecznie wywołuje router.transitionTo()na przykład router z rekwizytami dałeś link, który wygląda tak:

  /**
   * Transitions to the URL specified in the arguments by pushing
   * a new URL onto the history stack.
   */
  transitionTo: function (to, params, query) {
    var path = this.makePath(to, params, query);

    if (pendingTransition) {
      // Replace so pending location does not stay in history.
      location.replace(path);
    } else {
      location.push(path);
    }
  },

W przypadku zwykłego łącza ostatecznie wywołuje to location.push()dowolny używany typ lokalizacji, który obsługuje szczegóły konfiguracji historii, więc nawigacja za pomocą przycisków Wstecz i Dalej będzie działać, a następnie oddzwoni, aby router.handleLocationChange()router wiedział, że może przejść do przejścia do nowa ścieżka adresu URL.

Następnie router wywołuje własną router.dispatch()metodę z nowym adresem URL, który obsługuje szczegóły określania, która ze skonfigurowanych tras jest zgodna z adresem URL, a następnie wywołuje wszelkie zaczepy przejścia obecne dla dopasowanych tras. Możesz zaimplementować te haki przejścia na dowolnym z programów obsługi tras, aby podjąć pewne działania, gdy trasa ma być opuszczona lub do której ma być nawigowana, z możliwością przerwania przejścia, jeśli coś Ci się nie podoba.

Jeśli przejście nie zostało przerwane, ostatnim krokiem jest wywołanie wywołania zwrotnego, któremu dałeś, Router.run()z komponentem obsługi najwyższego poziomu i obiektem stanu ze wszystkimi szczegółami adresu URL i dopasowanych tras. Komponent obsługi najwyższego poziomu jest w rzeczywistości Routersamą instancją, która obsługuje renderowanie najwyższego poziomu obsługi trasy, który został dopasowany.

Powyższy proces jest uruchamiany ponownie za każdym razem, gdy przejdziesz do nowego adresu URL na kliencie.

Przykładowe projekty

Jonny Buchanan
źródło
3
Więc mógłbym prawdopodobnie powiedzieć, że routing klienta jest obsługiwany przez javascript (który jest kodem reagującego routera), jeśli jest obecny. Za każdym razem, gdy naciskam Enter na pasku adresu przeglądarki, odświeżam stronę lub wyłączam JS, strona serwera zajmie się routingiem. Z drugiej strony, gdy javascript jest gotowy na bieżącej stronie, routing będzie obsługiwany po stronie klienta. Czy dobrze zrozumiałem?
heartmon
9
Co znajduje się w module tras var routes = require('./routes')Czy jest to lista tras? Użyłem routera Express, ale ten przykład tutaj na SO wydaje się być jedynym przykładem konfiguracji renderowania po stronie serwera za pomocą React Router, więc byłoby dobrze, gdyby był to pełny przykład kodu
svnm
2
Powinna to być lista tras. Dodam o tym notatkę i kilka linków do przykładowych projektów.
Jonny Buchanan
2
Jeśli więc react-router zajmuje się routingiem po stronie serwera, to kto rozmawia z bazą danych? co się dzieje z routingiem po stronie serwera? wyobraź sobie, że chcemy zapewnić REST API dla natywnej aplikacji mobilnej. Kto się tym zajmie?
Morteza Shahriari Nia
1
Odpowiedź jest nieaktualna z powodu nowszej react-routerwersji. Zaktualizuj go.
oleh.meleshko
26

W wersji 1.0, React-Router jest zależny od modułu historii jako peerDependency. Ten moduł zajmuje się routingiem w przeglądarce. Domyślnie React-Router używa API historii HTML5 ( pushState, replaceState), ale możesz go skonfigurować tak, aby używał routingu opartego na hashach (patrz poniżej)

Obsługa tras odbywa się teraz za kulisami, a ReactRouter wysyła nowe właściwości do programów obsługi tras, gdy trasa się zmienia. Router ma nowe onUpdatewywołanie zwrotne właściwości <title>za każdym razem, gdy zmienia się trasa, przydatne na przykład do śledzenia odsłon lub aktualizacji .

Klient (routing HTML5)

import {Router} from 'react-router'
import routes from './routes'

var el = document.getElementById('root')

function track(){
  // ...
}

// routes can be children
render(<Router onUpdate={track}>{routes}</Router>, el)

Klient (routing na podstawie skrótu)

import {Router} from 'react-router'
import {createHashHistory} from 'history'
import routes from './routes'

var el = document.getElementById('root')

var history = createHashHistory()

// or routes can be a prop
render(<Router routes={routes} history={history}></Router>, el)

serwer

Na serwerze możemy użyć ReactRouter.match, jest to pobierane z przewodnika renderowania serwera

import { renderToString } from 'react-dom/server'
import { match, RoutingContext } from 'react-router'
import routes from './routes'

app.get('*', function(req, res) {
  // Note that req.url here should be the full URL path from
  // the original request, including the query string.
  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    if (error) {
      res.status(500).send(error.message)
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
      res.status(200).send(renderToString(<RoutingContext {...renderProps} />))
    } else {
      res.status(404).send('Not found')
    }
  })
})
Tomek
źródło