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

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

Что приходит на ум это:

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

2 ответа

Решение

Обратите внимание, что этот ответ охватывает 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.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 событие для запуска навигации.

Когда вы используете ответ-роутер <Link> компонент, вы даете ему to реквизит, который является названием маршрута, плюс любой 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-адресу на клиенте.

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

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

Обработка маршрута теперь выполняется за кулисами, и ReactRouter отправляет новые реквизиты в обработчики маршрутов при изменении маршрута. В роутере появился новый onUpdate Пропуск обратного вызова при изменении маршрута, полезен для отслеживания просмотра страниц или обновления <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')
    }
  })
})
Другие вопросы по тегам