Routage client (à l'aide de react-router) et routage côté serveur

108

J'ai réfléchi et je suis confus avec le routage entre le client et le serveur. Supposons que j'utilise ReactJS pour le rendu côté serveur avant de renvoyer la demande au navigateur Web, et que j'utilise react-router comme routage côté client pour basculer entre les pages sans actualiser en tant que SPA.

Ce qui me vient à l'esprit est:

  • Comment les itinéraires sont-ils interprétés? Par exemple, une requête de la page d'accueil ( /home) à la page des articles ( /posts)
  • Où va le routage, côté serveur ou client?
  • Comment sait-il comment il est traité?
mon coeur
la source
1
Je suggère de lire sur l'API History dans les navigateurs.
WiredPrairie

Réponses:

137

Notez que cette réponse couvre la version 0.13.x de React Router - la prochaine version 1.0 semble avoir des détails d'implémentation très différents

Serveur

C'est un minimum server.jsavec 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})
  })
})

Où le routesmodule exporte une liste de routes:

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

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

Chaque fois qu'une demande est faite au serveur, vous créez une Routerinstance à usage unique configurée avec l'URL entrante comme emplacement statique, qui est résolue par rapport à l'arborescence des routes pour configurer les routes correspondantes appropriées, en rappelant avec le niveau supérieur gestionnaire de route à rendre et un enregistrement des routes enfants correspondant à chaque niveau. C'est ce qui est consulté lorsque vous utilisez le <RouteHandler>composant dans un composant de gestion d'itinéraire pour rendre un itinéraire enfant qui a été mis en correspondance.

Si l'utilisateur a désactivé JavaScript ou que son chargement est lent, tous les liens sur lesquels il clique atteindront à nouveau le serveur, ce qui est résolu à nouveau comme ci-dessus.

Client

C'est un minimum client.jsavec react-router (réutiliser le même module routes):

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

Lorsque vous appelez Router.run(), il crée une instance de routeur pour vous dans les coulisses, qui est réutilisée chaque fois que vous naviguez dans l'application, car l'URL peut être dynamique sur le client, par opposition au serveur où une seule demande a un URL fixe.

Dans ce cas, nous utilisons le HistoryLocation, qui utilise l' HistoryAPI pour nous assurer que la bonne chose se passe lorsque vous appuyez sur le bouton Précédent / Suivant. Il y a aussi un HashLocationqui change l'URL hashpour faire des entrées d'historique et écoute l' window.onhashchangeévénement pour déclencher la navigation.

Lorsque vous utilisez le <Link>composant de react-router , vous lui donnez un toaccessoire qui est le nom d'une route, plus les données paramset queryles besoins de la route. Le <a>rendu par ce composant a un onClickgestionnaire qui appelle finalement router.transitionTo()l'instance de routeur avec les accessoires que vous avez donnés au lien, qui ressemble à ceci:

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

Pour un lien régulier, cela appelle finalement location.push()le type d'emplacement que vous utilisez, qui gère les détails de la configuration de l'historique afin que la navigation avec les boutons Précédent et Suivant fonctionnera, puis rappelle à router.handleLocationChange()pour informer le routeur qu'il peut procéder à la transition vers le nouveau chemin d'URL.

Le routeur appelle ensuite sa propre router.dispatch()méthode avec la nouvelle URL, qui gère les détails pour déterminer laquelle des routes configurées correspond à l'URL, puis appelle tous les hooks de transition présents pour les routes correspondantes. Vous pouvez implémenter ces hooks de transition sur n'importe lequel de vos gestionnaires d'itinéraire pour prendre des mesures lorsqu'une route est sur le point d'être parcourue ou parcourue, avec la possibilité d'annuler la transition si les choses ne vous conviennent pas.

Si la transition n'a pas été abandonnée, la dernière étape consiste à appeler le rappel que vous avez donné Router.run()avec le composant de gestionnaire de niveau supérieur et un objet d'état avec tous les détails de l'URL et les routes correspondantes. Le composant de gestionnaire de niveau supérieur est en fait l' Routerinstance elle-même, qui gère le rendu du gestionnaire de route le plus haut correspondant.

Le processus ci-dessus est réexécuté chaque fois que vous accédez à une nouvelle URL sur le client.

Exemples de projets

Jonny Buchanan
la source
3
Donc je pourrais probablement dire que le routage client est géré par javascript (qui est le code react-router) s'il présente. Chaque fois que j'appuie sur Entrée dans la barre d'adresse du navigateur, actualise la page ou désactive JS, le côté serveur gérera le routage. En revanche, lorsque le javascript est prêt sur la page en cours, le routage sera géré par le client. Ai-je bien compris?
heartmon
9
Que contient le module var routes = require('./routes')routes? Est-ce une liste de routes? J'ai utilisé un routeur Express mais cet exemple ici sur SO semble être le seul exemple de configuration du rendu côté serveur avec React Router, donc ce serait bien s'il s'agissait d'un exemple de code complet
svnm
2
Ce devrait être une liste d'itinéraires. J'ajouterai une note à ce sujet et des liens vers des exemples de projets.
Jonny Buchanan
2
Donc, si react-router s'occupe du routage côté serveur, qui parle à la base de données? qu'arrive-t-il au routage côté serveur? Imaginez que nous souhaitons fournir une API REST pour une application mobile native. Qui s'occupe de ça?
Morteza Shahriari Nia
1
La réponse est obsolète en raison d'une react-routerversion plus récente. Veuillez le mettre à jour.
oleh.meleshko
26

Avec 1.0, React-Router dépend du module d' historique en tant que peerDependency. Ce module traite du routage dans le navigateur. Par défaut, React-Router utilise l'API HTML5 History ( pushState, replaceState), mais vous pouvez le configurer pour utiliser le routage basé sur le hachage (voir ci-dessous)

La gestion de la route est maintenant effectuée dans les coulisses, et ReactRouter envoie de nouvelles props aux gestionnaires de route lorsque la route change. Le routeur a un nouveau onUpdaterappel de prop chaque fois qu'une route change, utile pour le suivi des pages vues ou la mise à jour de <title>, par exemple.

Client (routage 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 (routage basé sur le hachage)

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)

Serveur

Sur le serveur, nous pouvons utiliser ReactRouter.match, ceci est tiré du guide de rendu du serveur

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')
    }
  })
})
à M
la source