Routing client (utilizzando React-router) e Routing lato server


108

Ho pensato e sono confuso con il routing tra client e server. Supponiamo che io usi ReactJS per il rendering lato server prima di inviare la richiesta di nuovo al browser web, e uso il router reattivo come instradamento lato client per passare da una pagina all'altra senza aggiornare come SPA.

Quello che mi viene in mente è:

  • Come vengono interpretati i percorsi? Ad esempio, una richiesta dalla Home page ( /home) alla pagina Post ( /posts)
  • Dove va il routing, lato server o client?
  • Come fa a sapere come viene elaborato?

1
Suggerirei di leggere l'API della cronologia nei browser.
WiredPrairie

Risposte:


137

Nota, questa risposta copre la versione 0.13.x di React Router: la prossima versione 1.0 sembra che avrà dettagli di implementazione significativamente diversi

server

Questo è un minimo server.jscon 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})
  })
})

Dove il routesmodulo esporta un elenco di rotte:

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

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

Ogni volta che viene effettuata una richiesta al server, si crea Routerun'istanza monouso configurata con l'URL in entrata come posizione statica, che viene risolta rispetto all'albero delle rotte per impostare le rotte corrispondenti appropriate, richiamando con il livello superiore gestore delle rotte di cui eseguire il rendering e una registrazione delle rotte secondarie corrispondenti a ciascun livello. Questo è ciò che viene consultato quando usi il file<RouteHandler> componente all'interno di un componente di gestione delle rotte per eseguire il rendering di una rotta figlio che è stata abbinata.

Se l'utente ha disattivato JavaScript o il caricamento è lento, qualsiasi collegamento su cui fa clic raggiungerà nuovamente il server, il che verrà risolto nuovamente come sopra.

Cliente

Questo è un minimo client.jscon React-Router (riutilizzo dello stesso modulo route):

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

Quando chiami Router.run() , crea un'istanza del router dietro le quinte, che viene riutilizzata ogni volta che navighi nell'app, poiché l'URL può essere dinamico sul client, invece che sul server in cui una singola richiesta ha un URL fisso.

In questo caso, stiamo usando HistoryLocation, che utilizza l' HistoryAPI per assicurarci che accada la cosa giusta quando premi il pulsante Indietro / Avanti. C'è anche un HashLocationche cambia l'URL hashper inserire voci nella cronologia e ascolta l' window.onhashchangeevento per attivare la navigazione.

Quando usi il <Link>componente di react-router , gli dai un toprop che è il nome di una rotta, più qualsiasi dato paramse di cui ha querybisogno la rotta. Il <a>rendering da questo componente ha un onClickgestore che alla fine chiama router.transitionTo()l'istanza del router con gli oggetti di scena che hai fornito al collegamento, che assomiglia a questo:

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

Per un collegamento normale, questo alla fine chiama location.push()il tipo di posizione che stai utilizzando, che gestisce i dettagli della configurazione della cronologia, quindi la navigazione con i pulsanti Indietro e Avanti funzionerà, quindi richiama per router.handleLocationChange()far sapere al router che può procedere con la transizione a il nuovo percorso URL.

Il router chiama quindi il proprio router.dispatch()metodo con il nuovo URL, che gestisce i dettagli per determinare quale delle rotte configurate corrisponde all'URL, quindi chiama eventuali hook di transizione presenti per le rotte corrispondenti. Puoi implementare questi hook di transizione su qualsiasi gestore di rotta per intraprendere un'azione quando una rotta sta per essere spostata o verso di essa, con la possibilità di interrompere la transizione se le cose non sono di tuo gradimento.

Se la transizione non è stata interrotta, il passaggio finale consiste nel richiamare il callback a cui hai fornito Router.run()il componente gestore di primo livello e un oggetto di stato con tutti i dettagli dell'URL e le rotte corrispondenti. Il componente gestore di primo livello è in realtà l' Routeristanza stessa, che gestisce il rendering del gestore di rotte più in alto che è stato abbinato.

Il processo di cui sopra viene rieseguito ogni volta che si accede a un nuovo URL sul client.

Progetti di esempio


3
Quindi potrei probabilmente dire che il routing del client è gestito da javascript (che è il codice del router di reazione) se presente. Ogni volta che premo Invio sulla barra degli indirizzi del browser o aggiorno la pagina o disabilito JS, il lato server gestirà il routing. D'altra parte, quando il javascript è pronto nella pagina corrente, il routing verrà gestito dal lato client. Ho capito bene?
heartmon

9
Cosa c'è nel modulo rotte var routes = require('./routes')È un elenco di rotte? Ho usato il router Express ma questo esempio qui su SO sembra essere l'unico esempio di configurazione del rendering lato server con React Router, quindi sarebbe bene se fosse un esempio di codice completo
svnm

2
Dovrebbe essere un elenco di percorsi. Aggiungerò una nota al riguardo e alcuni collegamenti a progetti di esempio.
Jonny Buchanan

2
Quindi, se react-router si occupa del routing lato server, chi parla con il database? cosa succede al routing lato server? immagina di voler fornire un'API REST per un'app mobile nativa. Chi se ne occupa?
Morteza Shahriari Nia

1
La risposta è obsoleta a causa della react-routerversione più recente. Per favore aggiornalo.
oleh.meleshko

26

Con 1.0, React-Router dipende dal modulo della cronologia come peerDependency. Questo modulo si occupa del routing nel browser. Per impostazione predefinita, React-Router utilizza l'API cronologia HTML5 ( pushState, replaceState), ma puoi configurarlo per utilizzare il routing basato su hash (vedi sotto)

La gestione della rotta ora viene eseguita dietro le quinte e ReactRouter invia nuovi oggetti di scena ai gestori della rotta quando la rotta cambia. Il router ha un nuovo onUpdatecallback prop ogni volta che cambia un percorso, utile per il monitoraggio delle visualizzazioni di pagina o per l'aggiornamento <title>, ad esempio, del file.

Client (instradamento 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)

Client (instradamento basato su hash)

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

Sul server, possiamo usare ReactRouter.match, questo è preso dalla guida al rendering del server

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')
    }
  })
})
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.