Client-Routing (mit React-Router) und serverseitiges Routing

108

Ich habe nachgedacht und bin verwirrt mit dem Routing zwischen Client und Server. Angenommen, ich verwende ReactJS für das serverseitige Rendern, bevor ich die Anforderung an den Webbrowser zurücksende, und verwende den React-Router als clientseitiges Routing, um zwischen Seiten zu wechseln, ohne als SPA zu aktualisieren.

Was mir in den Sinn kommt, ist:

  • Wie werden die Routen interpretiert? Zum Beispiel eine Anfrage von Homepage ( /home) zu Postseite ( /posts)
  • Wohin geht das Routing, auf der Serverseite oder auf dem Client?
  • Woher weiß es, wie es verarbeitet wird?
Herzmon
quelle
1
Ich würde vorschlagen, die Verlaufs-API in Browsern zu lesen.
WiredPrairie

Antworten:

137

Beachten Sie, dass diese Antwort die React Router-Version 0.13.x abdeckt. Die kommende Version 1.0 scheint deutlich andere Implementierungsdetails zu haben

Server

Dies ist ein Minimum server.jsbei React-Router:

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

Wo das routesModul eine Liste von Routen exportiert:

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

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

Jedes Mal, wenn eine Anforderung an den Server gesendet wird, erstellen Sie eine RouterEinweginstanz, die mit der eingehenden URL als statischem Speicherort konfiguriert ist. Diese wird anhand des Routenbaums aufgelöst, um die entsprechenden übereinstimmenden Routen einzurichten, und ruft mit der obersten Ebene zurück Routenhandler, der gerendert werden soll, und Aufzeichnung, welche untergeordneten Routen auf jeder Ebene übereinstimmen. Dies wird empfohlen, wenn Sie die <RouteHandler>Komponente innerhalb einer Routenbehandlungskomponente verwenden, um eine untergeordnete Route zu rendern, die übereinstimmt.

Wenn der Benutzer JavaScript deaktiviert hat oder das Laden langsam ist, werden alle Links, auf die er klickt, erneut auf den Server übertragen, was wie oben beschrieben erneut behoben wird.

Klient

Dies ist ein Minimum client.jsbei React-Router (Wiederverwendung des gleichen Routenmoduls):

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

Wenn Sie anrufen Router.run(), wird hinter den Kulissen eine Router-Instanz für Sie erstellt, die bei jeder Navigation in der App wiederverwendet wird, da die URL auf dem Client dynamisch sein kann und nicht auf dem Server, auf dem eine einzelne Anforderung eine hat feste URL.

In diesem Fall verwenden wir die HistoryLocation, die die HistoryAPI verwendet , um sicherzustellen, dass das Richtige passiert, wenn Sie auf die Schaltfläche Zurück / Vorwärts klicken. Es gibt auch eine, HashLocationdie die URL ändert hash, um Verlaufseinträge vorzunehmen, und das window.onhashchangeEreignis abhört, um die Navigation auszulösen.

Wenn Sie reagieren-Router verwenden <Link>Komponente, geben Sie ihm eine toStütze , die den Namen einer Route ist, sowie alle paramsund querydie Routen Daten benötigt. Das <a>von dieser Komponente gerenderte hat einen onClickHandler, der letztendlich router.transitionTo()die Router-Instanz mit den Requisiten aufruft, die Sie dem Link gegeben haben. Das sieht folgendermaßen aus:

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

Bei einer regulären Verbindung wird letztendlich der location.push()von Ihnen verwendete Standorttyp aufgerufen, der die Details zum Einrichten des Verlaufs behandelt, sodass die Navigation mit den Schaltflächen "Zurück" und "Vorwärts" funktioniert. Anschließend wird zurückgerufen router.handleLocationChange(), um dem Router mitzuteilen, dass der Übergang zu fortgesetzt werden kann der neue URL-Pfad.

Der Router ruft dann seine eigene router.dispatch()Methode mit der neuen URL auf, die die Details zum Bestimmen festlegt, welche der konfigurierten Routen mit der URL übereinstimmen, und ruft dann alle für die übereinstimmenden Routen vorhandenen Übergangshooks auf. Sie können diese Übergangshooks in jedem Ihrer Routenhandler implementieren, um Maßnahmen zu ergreifen, wenn eine Route entfernt oder navigiert werden soll. Sie können den Übergang abbrechen, wenn die Dinge nicht Ihren Wünschen entsprechen.

Wenn der Übergang nicht abgebrochen wurde, besteht der letzte Schritt darin, den Rückruf aufzurufen, den Sie Router.run()mit der Handlerkomponente der obersten Ebene und einem Statusobjekt mit allen Details der URL und den übereinstimmenden Routen erhalten haben. Die Handlerkomponente der obersten Ebene ist eigentlich die RouterInstanz selbst, die das Rendern des obersten Routenhandlers übernimmt, der abgeglichen wurde.

Der obige Vorgang wird jedes Mal erneut ausgeführt, wenn Sie zu einer neuen URL auf dem Client navigieren.

Beispielprojekte

Jonny Buchanan
quelle
3
Ich könnte also wahrscheinlich sagen, dass das Client-Routing von Javascript (dem React-Router-Code) verwaltet wird, wenn es vorhanden ist. Immer wenn ich in der Adressleiste des Browsers die Eingabetaste drücke oder die Seite aktualisiere oder JS deaktiviere, übernimmt die Serverseite das Routing. Wenn das Javascript auf der aktuellen Seite bereit ist, wird das Routing von der Clientseite übernommen. Habe ich richtig verstanden
Heartmon
9
Was befindet sich im var routes = require('./routes')Routenmodul? Ist es eine Liste von Routen? Ich habe Express-Router verwendet, aber dieses Beispiel hier auf SO scheint das einzige Beispiel für das Einrichten des serverseitigen Renderns mit React Router zu sein. Es wäre also gut, wenn es ein vollständiges Codebeispiel wäre
svnm
2
Es sollte eine Liste von Routen sein. Ich werde einen Hinweis dazu und einige Links zu Beispielprojekten hinzufügen.
Jonny Buchanan
2
Wenn sich der React-Router um das serverseitige Routing kümmert, wer spricht dann mit der Datenbank? Was passiert mit dem serverseitigen Routing? Stellen Sie sich vor, wir möchten eine REST-API für eine native mobile App bereitstellen. Wer kümmert sich darum?
Morteza Shahriari Nia
1
Die Antwort ist aufgrund der neueren react-routerVersion veraltet . Bitte aktualisieren Sie es.
oleh.meleshko
26

Mit 1,0, Reaktion-Router ist abhängig von der Historie - Modul als peerDependency. Dieses Modul behandelt das Routing im Browser. Standardmäßig verwendet React-Router die HTML5-Verlaufs-API ( pushState, replaceState), Sie können sie jedoch für die Verwendung von Hash-basiertem Routing konfigurieren (siehe unten).

Die Routenbehandlung erfolgt jetzt hinter den Kulissen, und ReactRouter sendet neue Requisiten an die Routenhandler, wenn sich die Route ändert. Der Router verfügt über einen neuen onUpdateProp-Rückruf, wenn sich eine Route ändert. Dies ist <title>beispielsweise nützlich für die Seitenaufrufverfolgung oder die Aktualisierung der Route .

Client (HTML5-Routing)

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)

Client (Hash-basiertes Routing)

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)

Server

Auf dem Server können wir verwenden ReactRouter.match, dies wird aus dem Server-Rendering-Handbuch entnommen

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')
    }
  })
})
Tom
quelle