Маршрутизация клиента (с использованием адаптера-ответчика) и маршрутизации на стороне сервера

Я думал, и я путаюсь с маршрутизацией между клиентом и сервером. Предположим, что я использую ReactJS для рендеринга на стороне сервера, прежде чем отправлять запрос обратно в веб-браузер, и использовать ретранслятор-маршрутизатор в качестве маршрутизации на стороне клиента для переключения между страницами без обновления как SPA.

Что приходит в голову:

  • Как интерпретируются маршруты? Например, с домашней страницы (/home) при запросе на страницу "Почта" (/posts)
  • Где идет маршрутизация на стороне сервера или клиента?
  • Как он знает, как он обрабатывается?

Ответ 1

Примечание. Этот ответ охватывает версию React Router версии 0.13.x - предстоящая версия 1.0, похоже, что она будет иметь значительно разные детали реализации

Сервер

Это минимальный server.js с реактивным маршрутизатором:

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

Если модуль routes экспортирует список маршрутов:

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

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

Каждый раз, когда запрос отправляется на сервер, вы создаете одноразовый экземпляр Router, настроенный с входящим URL в качестве его статического местоположения, который разрешен против дерева маршрутов для настройки соответствующих согласованных маршрутов, вызывая назад с обработчиком маршрута верхнего уровня, который будет отображаться, и запись о том, какие дочерние маршруты соответствуют каждому уровню. Это то, что было проконсультировано, когда вы используете компонент <RouteHandler> в компоненте обработки маршрута для отображения дочернего маршрута, который был сопоставлен.

Если у пользователя отключен JavaScript или он медленно загружается, любые ссылки, на которые они нажимают, снова попадут на сервер, который будет снова разрешен, как указано выше.

Client

Это минимальный client.js с реактивным маршрутизатором (повторное использование одного и того же маршрутного модуля):

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

Когда вы вызываете Router.run(), он создает экземпляр Router для вас за кулисами, который повторно используется при каждом перемещении по приложению, поскольку URL-адрес может быть динамическим на клиенте, а не на сервере где один запрос имеет фиксированный URL.

В этом случае мы используем HistoryLocation, который использует History API, чтобы убедиться, что правильная вещь случится, когда вы нажмете назад/вперед. Также существует HashLocation, который изменяет URL hash, чтобы сделать записи истории и прослушивать событие window.onhashchange, чтобы вызвать навигацию.

Когда вы используете компонент response-router <Link>, вы даете ему to prop, который является именем маршрута, плюс любые данные params и query, необходимые для маршрута. <a>, отображаемый этим компонентом, имеет обработчик onClick, который в конечном итоге вызывает router.transitionTo() на экземпляре маршрутизатора с помощью реквизита, который вы указали на ссылку, которая выглядит следующим образом:

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

Для обычной ссылки это в конечном итоге вызывает location.push() в зависимости от того, какой тип местоположения вы используете, который обрабатывает детали настройки истории, поэтому работа с кнопками "назад" и "вперед" будет работать, а затем возвращается к router.handleLocationChange(), чтобы позволить маршрутизатор знает, что он может перейти к новому пути URL.

Затем маршрутизатор вызывает свой собственный метод router.dispatch() с новым URL-адресом, который обрабатывает детали определения того, какой из настроенных маршрутов соответствует URL-адресу, а затем вызывает любые переходные переходы присутствует для согласованных маршрутов. Вы можете реализовать эти переходные перехватчики на любом из ваших обработчиков маршрутов, чтобы предпринять какие-то действия, когда маршрут будет перемещен в сторону или переместиться на него, с возможностью прервать переход, если вам не по душе.

Если переход не был прерван, последним шагом будет вызов обратного вызова, который вы передали Router.run(), с компонентом обработчика верхнего уровня и объектом состояния со всеми подробностями URL-адреса и согласованных маршрутов. Компонент верхнего уровня - фактически сам экземпляр Router, который обрабатывает рендеринг самого обработчика маршрута, который был сопоставлен.

Вышеупомянутый процесс повторно запускается каждый раз, когда вы переходите к новому URL-адресу на клиенте.

Примеры проектов

Ответ 2

С 1.0, React-Router зависит от модуля history как peerDependency. Этот модуль касается маршрутизации в браузере. По умолчанию React-Router использует API истории HTML5 (pushState, replaceState), но вы можете настроить его на использование хэш-маршрутизации (см. Ниже)

Обработка маршрута теперь выполняется за кулисами, а ReactRouter отправляет новые реквизиты вниз к обработчикам маршрута при изменении маршрута. Маршрутизатор имеет новый обратный вызов onUpdate prop при каждом изменении маршрута, полезный для отслеживания просмотров страницы или, например, обновление <title>.

Клиент (маршрутизация 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)

Клиент (маршрутизация на основе хешей)

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)

Сервер

На сервере мы можем использовать ReactRouter.match, это взято из руководства по рендерингу сервера

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