Roteamento de cliente (usando react-router) e roteamento do lado do servidor

108

Andei pensando e me confundi com o roteamento entre Cliente e Servidor. Suponha que eu use ReactJS para renderização do lado do servidor antes de enviar a solicitação de volta ao navegador da web, e use o react-router como um roteamento do lado do cliente para alternar entre as páginas sem atualizar como SPA.

O que vem à mente é:

  • Como as rotas são interpretadas? Por exemplo, uma solicitação da página inicial ( /home) para a página de postagens ( /posts)
  • Para onde vai o roteamento, no lado do servidor ou no cliente?
  • Como sabe como é processado?
Heartmon
fonte
1
Eu sugiro ler sobre a API de histórico em navegadores.
WiredPrairie de

Respostas:

137

Note, esta resposta cobre React Router versão 0.13.x - a próxima versão 1.0 parece que terá detalhes de implementação significativamente diferentes

Servidor

Isso é mínimo server.jscom 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})
  })
})

Onde o routesmódulo exporta uma lista de Rotas:

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

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

Cada vez que uma solicitação é feita ao servidor, você cria uma Routerinstância de uso único configurada com o URL de entrada como seu local estático, que é resolvido na árvore de rotas para configurar as rotas correspondentes adequadas, chamando de volta com o nível superior o manipulador de rotas a ser renderizado e um registro de quais rotas secundárias correspondem em cada nível. Isso é o que é consultado quando você usa o <RouteHandler>componente dentro de um componente de manipulação de rota para renderizar uma rota secundária que foi correspondida.

Se o usuário tiver o JavaScript desativado ou se ele estiver demorando para carregar, todos os links em que ele clicar chegarão ao servidor novamente, o que é resolvido como descrito acima.

Cliente

Isso é mínimo client.jscom react-router (reutilizando o mesmo módulo de rotas):

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 você chama Router.run(), ele cria uma instância de Roteador para você nos bastidores, que é reutilizada toda vez que você navega pelo aplicativo, já que a URL pode ser dinâmica no cliente, ao contrário do servidor onde uma única solicitação tem um URL fixo.

Nesse caso, estamos usando o HistoryLocation, que usa a HistoryAPI para garantir que a coisa certa aconteça quando você clicar no botão voltar / avançar. Há também um HashLocationque altera o URL hashpara fazer entradas de histórico e ouve o window.onhashchangeevento para acionar a navegação.

Quando você usa de reagir-router <Link>componente, você dar-lhe um tosuporte que é o nome de uma rota, além de quaisquer paramse queryde dados das necessidades de rota. O <a>renderizado por este componente tem um onClickmanipulador que, em última análise, chama router.transitionTo()a instância do roteador com os props que você forneceu ao link, que se parece com isto:

  /**
   * 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 um link regular, isso acaba chamando location.push()qualquer tipo de local que você está usando, que lida com os detalhes de configuração do histórico para que a navegação com os botões voltar e avançar funcione e, em seguida, chama de volta para router.handleLocationChange()para informar ao roteador que pode prosseguir com a transição para o novo caminho de URL.

O roteador então chama seu próprio router.dispatch()método com a nova URL, que lida com os detalhes de determinar quais das rotas configuradas correspondem à URL, a seguir chama quaisquer ganchos de transição presentes para as rotas correspondentes. Você pode implementar esses ganchos de transição em qualquer um de seus manipuladores de rota para realizar alguma ação quando uma rota estiver prestes a ser navegada ou afastada, com a capacidade de abortar a transição se as coisas não forem do seu agrado.

Se a transição não foi abortada, a etapa final é chamar o retorno de chamada que você deu Router.run()com o componente manipulador de nível superior e um objeto de estado com todos os detalhes do URL e as rotas correspondentes. O componente manipulador de nível superior é, na verdade, a Routerprópria instância, que lida com a renderização do manipulador de rota superior que foi correspondido.

O processo acima é executado novamente sempre que você navegar para um novo URL no cliente.

Projetos de exemplo

Jonny Buchanan
fonte
3
Portanto, eu provavelmente poderia dizer que o roteamento do cliente é tratado por javascript (que é o código reac-roteador) se houver. Sempre que eu pressiono Enter na barra de endereço do navegador, atualizo a página ou desabilito o JS, o servidor fará o roteamento. Por outro lado, quando o javascript estiver pronto na página atual, o roteamento será feito pelo cliente. Eu entendi corretamente?
heartmon
9
O que está no módulo de rotas var routes = require('./routes')É uma lista de rotas? Eu usei o roteador Express, mas este exemplo aqui no SO parece ser o único exemplo de configuração da renderização do lado do servidor com o React Router, então seria bom se fosse um exemplo de código completo
svnm
2
Deve ser uma lista de rotas. Adicionarei uma nota sobre isso e alguns links para projetos de exemplo.
Jonny Buchanan
2
Portanto, se o react-router cuida do roteamento do lado do servidor, quem está falando com o banco de dados? o que acontece com o roteamento do lado do servidor? imagine que queremos fornecer uma API REST para um aplicativo móvel nativo. Quem cuida disso?
Morteza Shahriari Nia
1
A resposta está desatualizada devido à react-routerversão mais recente. Por favor, atualize-o.
oleh.meleshko
26

Com o 1.0, o React-Router depende do módulo de histórico como um peerDependency. Este módulo trata do roteamento no navegador. Por padrão, o React-Router usa a API HTML5 History ( pushState, replaceState), mas você pode configurá-lo para usar o roteamento baseado em hash (veja abaixo)

O tratamento da rota agora é feito nos bastidores e o ReactRouter envia novos adereços para os manipuladores da rota quando a rota muda. O roteador tem um novo onUpdateretorno de chamada prop sempre que uma rota muda, útil para rastreamento de exibição de página ou atualização de <title>, por exemplo.

Cliente (roteamento 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 (roteamento baseado em 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

No servidor, podemos usar ReactRouter.match, isso é retirado do guia de renderização do 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
fonte