Enrutamiento del cliente (usando react-router) y enrutamiento del lado del servidor

108

He estado pensando y estoy confundido con el enrutamiento entre Cliente y Servidor. Supongamos que uso ReactJS para la representación del lado del servidor antes de enviar la solicitud al navegador web, y uso react-router como un enrutamiento del lado del cliente para cambiar entre páginas sin actualizar como SPA.

Lo que me viene a la mente es:

  • ¿Cómo se interpretan las rutas? Por ejemplo, una solicitud de la página de inicio ( /home) a la página de publicaciones ( /posts)
  • ¿A dónde va el enrutamiento, en el lado del servidor o en el cliente?
  • ¿Cómo sabe cómo se procesa?
corazon
fuente
1
Sugeriría leer sobre la API de historial en los navegadores.
WiredPrairie

Respuestas:

137

Tenga en cuenta que esta respuesta cubre React Router versión 0.13.x: la próxima versión 1.0 parece que tendrá detalles de implementación significativamente diferentes

Servidor

Este es un mínimo 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})
  })
})

Donde el routesmódulo exporta una lista de Rutas:

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

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

Cada vez que se realiza una solicitud al servidor, crea una Routerinstancia de un solo uso configurada con la URL entrante como su ubicación estática, que se resuelve contra el árbol de rutas para configurar las rutas coincidentes adecuadas, llamando con el nivel superior controlador de ruta que se va a representar y un registro de las rutas secundarias que coinciden en cada nivel. Esto es lo que se consulta cuando usa el <RouteHandler>componente dentro de un componente de manejo de ruta para representar una ruta secundaria que coincidió.

Si el usuario tiene JavaScript desactivado, o si se carga con lentitud, cualquier enlace en el que haga clic llegará al servidor nuevamente, lo que se resuelve nuevamente como se indicó anteriormente.

Cliente

Este es un mínimo client.jscon react-router (reutilizando el mismo módulo de rutas):

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

Cuando llama Router.run(), crea una instancia de enrutador detrás de escena, que se reutiliza cada vez que navega por la aplicación, ya que la URL puede ser dinámica en el cliente, a diferencia de en el servidor donde una sola solicitud tiene un URL fija.

En este caso, estamos usando HistoryLocation, que usa la HistoryAPI para asegurarse de que suceda lo correcto cuando presiona el botón Atrás / Adelante. También hay una HashLocationque cambia la URL hashpara realizar entradas en el historial y escucha el window.onhashchangeevento para activar la navegación.

Cuando se utiliza de reaccionar-enrutador <Link>componente, que le dan un topuntal que es el nombre de una ruta, además de cualquier paramsy queryde datos de las necesidades de ruta. El <a>renderizado por este componente tiene un onClickcontrolador que finalmente llama router.transitionTo()a la instancia del enrutador con los accesorios que le dio al enlace, que se ve así:

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

Para un enlace regular, esto finalmente llama location.push()al tipo de ubicación que esté utilizando, que maneja los detalles de la configuración del historial, por lo que la navegación con los botones de retroceso y avance funcionará, luego vuelve a llamar router.handleLocationChange()para informar al enrutador que puede continuar con la transición a la nueva ruta de URL.

Luego, el enrutador llama a su propio router.dispatch()método con la nueva URL, que maneja los detalles para determinar cuál de las rutas configuradas coincide con la URL, luego llama a cualquier enlace de transición presente para las rutas coincidentes. Puede implementar estos ganchos de transición en cualquiera de sus controladores de ruta para tomar alguna acción cuando una ruta esté a punto de ser navegada o alejada de ella, con la capacidad de abortar la transición si las cosas no son de su agrado.

Si la transición no se canceló, el paso final es llamar a la devolución de llamada que le dio Router.run()con el componente de controlador de nivel superior y un objeto de estado con todos los detalles de la URL y las rutas coincidentes. El componente de controlador de nivel superior es en realidad la Routerinstancia en sí, que maneja la representación del controlador de ruta más alto que se emparejó.

El proceso anterior se vuelve a ejecutar cada vez que navega a una nueva URL en el cliente.

Proyectos de ejemplo

Jonny Buchanan
fuente
3
Entonces, probablemente podría decir que el enrutamiento del cliente es manejado por javascript (que es el código del enrutador de reacción) si se presenta. Cada vez que presiono enter en la barra de direcciones del navegador o actualizo la página o deshabilito JS, el lado del servidor manejará el enrutamiento. Por otro lado, cuando el javascript esté listo en la página actual, el enrutamiento será manejado por el lado del cliente. ¿Entendí correctamente?
heartmon
9
¿Qué hay en el módulo var routes = require('./routes')de rutas? ¿Es una lista de rutas? He usado el enrutador Express, pero este ejemplo aquí en SO parece ser el único ejemplo de configuración de la representación del lado del servidor con React Router, por lo que sería bueno si fuera un ejemplo de código completo
svnm
2
Debería ser una lista de rutas. Agregaré una nota sobre eso y algunos enlaces a proyectos de ejemplo.
Jonny Buchanan
2
Entonces, si react-router se encarga del enrutamiento del lado del servidor, ¿quién habla con la base de datos? ¿Qué sucede con el enrutamiento del lado del servidor? Imagine que queremos proporcionar una API REST para una aplicación móvil nativa. ¿Quién se encarga de eso?
Morteza Shahriari Nia
1
La respuesta está desactualizada debido a una react-routerversión más reciente. Actualícelo.
oleh.meleshko
26

Con 1.0, React-Router depende del módulo de historial como peerDependency. Este módulo trata sobre el enrutamiento en el navegador. De forma predeterminada, React-Router usa la API de historial HTML5 ( pushState, replaceState), pero puede configurarla para usar enrutamiento basado en hash (ver más abajo)

El manejo de la ruta ahora se realiza detrás de escena, y ReactRouter envía nuevos accesorios a los controladores de ruta cuando la ruta cambia. El enrutador tiene una nueva onUpdatedevolución de llamada de apoyo cada vez que cambia una ruta, útil para el seguimiento de páginas vistas o para actualizar <title>, por ejemplo.

Cliente (enrutamiento 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)

Cliente (enrutamiento basado en 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)

Servidor

En el servidor, podemos usar ReactRouter.match, esto se toma de la guía de renderización del servidor

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
fuente