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

108

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

На ум приходит:

  • Как трактуются маршруты? Например, запрос с домашней страницы ( /home) на страницу сообщений (/posts )
  • Куда идет маршрутизация, на стороне сервера или на стороне клиента?
  • Как он узнает, как обрабатывается?
Heartmon
источник
1
Я бы посоветовал почитать History API в браузерах.
WiredPrairie

Ответы:

137

Обратите внимание, что этот ответ касается 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, который использует HistoryAPI, чтобы убедиться, что все происходит правильно, когда вы нажимаете кнопку назад / вперед. Также есть, HashLocationкоторый изменяет URL-адрес hashдля создания записей в истории и прослушивает window.onhashchangeсобытие для запуска навигации.

Когда вы используете <Link>компонент response-router , вы даете ему 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-адресу на клиенте.

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

Джонни Бьюкенен
источник
3
Поэтому я, вероятно, мог бы сказать, что маршрутизация клиентов обрабатывается javascript (который является кодом реагирующего маршрутизатора), если он присутствует. Каждый раз, когда я нажимаю Enter в адресной строке браузера, обновляю страницу или отключаю JS, серверная сторона обрабатывает маршрутизацию. С другой стороны, когда javascript готов на текущей странице, маршрутизация будет выполняться на стороне клиента. Правильно ли я понял ?
heartmon
9
Что находится в модуле маршрутов. var routes = require('./routes')Это список маршрутов? Я использовал маршрутизатор Express, но этот пример здесь, на SO, кажется, единственный пример настройки рендеринга на стороне сервера с помощью React Router, поэтому было бы хорошо, если бы это был пример полного кода
svnm
2
Это должен быть список маршрутов. Я добавлю примечание об этом и несколько ссылок на примеры проектов.
Джонни Бьюкенен,
2
Итак, если реактивный маршрутизатор позаботится о маршрутизации на стороне сервера, то кто будет разговаривать с базой данных? что происходит с маршрутизацией на стороне сервера? представьте, что мы хотим предоставить REST API для собственного мобильного приложения. Кто об этом позаботится?
Мортеза Шахриари Ниа
1
Ответ устарел из-за более новой react-routerверсии. Пожалуйста, обновите его.
oleh.meleshko
26

В версии 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')
    }
  })
})
Том
источник