客户端路由(使用react-router)和服务器端路由

时间:2022-04-01 15:55:29

I have been thinking and I am confused with the routing between Client and Server. Suppose I use ReactJS for server-side rendering before sending the request back to web browser, and use react-router as a client-side routing to switch between pages without refreshing as SPA.

我一直在想,我对Client和Server之间的路由感到困惑。假设我在将请求发送回Web浏览器之前使用ReactJS进行服务器端呈现,并使用react-router作为客户端路由在页面之间切换而不刷新为SPA。

What comes to mind is:

我想到的是:

  • How are the routes interpreted? For example, a request from Home page (/home) to Posts page (/posts)
  • 如何解释路线?例如,从主页(/ home)到帖子页面(/帖子)的请求
  • Where does the routing go, on server-side or client?
  • 路由在服务器端或客户端上的位置是什么?
  • How does it know how it is processed?
  • 它是如何知道如何处理的?

2 个解决方案

#1


128  

Note, this answer covers React Router version 0.13.x - the upcoming version 1.0 looks like it will have significantly different implementation details

注意,这个答案涵盖了React Router版本0.13.x - 即将推出的1.0版本看起来会有明显不同的实现细节

Server

This is a minimal server.js with react-router:

这是带有react-router的最小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})
  })
})

Where the routes module exports a list of Routes:

routes模块导出路由列表的位置:

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

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

Every time a request is made to the server, you create a single-use Router instance configured with the incoming URL as its static location, which is resolved against the tree of routes to set up the appropriate matched routes, calling back with the top-level route handler to be rendered and a record of which child routes matched at each level. This is what's consulted when you use the <RouteHandler> component within a route handling component to render a child route which was matched.

每次向服务器发出请求时,您都会创建一个一次性使用的路由器实例,该实例使用传入的URL作为其静态位置进行配置,该实例将根据路由树进行解析,以设置相应的匹配路由,并使用要呈现的级别路由处理程序以及在每个级别匹配的子路由的记录。当您在路由处理组件中使用 组件来呈现匹配的子路由时,这就是所咨询的内容。

If the user has JavaScript turned off, or it's being slow to load, any links they click on will hit the server again, which is resolved again as above.

如果用户关闭了JavaScript,或者加载速度很慢,则他们点击的任何链接都会再次点击服务器,如上所述再次解决。

Client

This is a minimal client.js with react-router (re-using the same routes module):

这是一个带有react-router的最小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)
})

When you call Router.run(), it creates a Router instance for you behind the scenes, which is re-used every time you navigate around the app, as the URL can be dynamic on the client, as opposed to on the server where a single request has a fixed URL.

当您调用Router.run()时,它会在幕后为您创建一个Router实例,每次您在应用程序中导航时都会重复使用该实例,因为URL可以在客户端上是动态的,而不是在服务器上单个请求具有固定的URL。

In this case, we're using the HistoryLocation, which uses the History API to make sure the right thing happens when you hit the back/forward button. There's also a HashLocation which changes the URL hash to make history entries and listens to the window.onhashchange event to trigger navigation.

在这种情况下,我们使用HistoryLocation,它使用History API来确保当您点击后退/前进按钮时正确的事情发生。还有一个HashLocation,它更改URL哈希以创建历史记录条目并侦听window.onhashchange事件以触发导航。

When you use react-router's <Link> component, you give it a to prop which is the name of a route, plus any params and query data the route needs. The <a> rendered by this component has an onClick handler which ultimately calls router.transitionTo() on the router instance with the props you gave the link, which looks like this:

当您使用react-router的 组件时,您可以为它提供一个prop,它是路由的名称,以及路由所需的任何params和查询数据。此组件呈现的具有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);
    }
  },

For a regular link this ultimately calls location.push() on whichever Location type you're using, which handles the details of setting up history so navigating with the back and forward buttons will work, then calls back to router.handleLocationChange() to let the router know it can proceed with transitioning to the new URL path.

对于常规链接,这最终会调用您正在使用的任何位置类型的location.push(),它处理设置历史记录的详细信息,因此使用后退和前进按钮进行导航将起作用,然后回调到router.handleLocationChange()让路由器知道它可以继续转换到新的URL路径。

The router then calls its own router.dispatch() method with the new URL, which handles the details of determining which of the configured routes match the URL, then calls any transition hooks present for the matched routes. You can implement these transition hooks on any of your route handlers to take some action when a route is about to be navigated away from or navigated to, with the ability to abort the transition if things aren't to your liking.

然后,路由器使用新URL调用自己的router.dispatch()方法,该URL处理确定哪些配置的路由与URL匹配的详细信息,然后调用匹配路由的任何转换挂钩。您可以在任何路由处理程序上实现这些转换挂钩,以便在路径即将导航或导航到路径时执行某些操作,并且能够在不符合您喜欢的情况下中止转换。

If the transition wasn't aborted, the final step is to call the callback you gave to Router.run() with the top-level handler component and a state object with all the details of the URL and the matched routes. The top-level handler component is actually the Router instance itself, which handles rendering the top-most route handler which was matched.

如果转换没有中止,最后一步是使用*处理程序组件调用您给Router.run()的回调,并使用URL和匹配路由的所有详细信息调用状态对象。*处理程序组件实际上是路由器实例本身,它处理渲染匹配的最顶层路由处理程序。

The above process is re-run every time you navigate to a new URL on the client.

每次导航到客户端上的新URL时,都会重新运行上述过程。

Example projects

#2


25  

With 1.0, React-Router depends on the history module as a peerDependency. This module deals with routing in the browser. By default React-Router uses the HTML5 History API (pushState, replaceState), but you can configure it to use hash-based routing (see below)

使用1.0,React-Router依赖于历史模块作为peerDependency。该模块处理浏览器中的路由。默认情况下,React-Router使用HTML5 History API(pushState,replaceState),但您可以将其配置为使用基于散列的路由(请参阅下文)

The route handling is now done behind the scenes, and ReactRouter sends new props down to the Route handlers when the route changes. The Router has a new onUpdate prop callback whenever a route changes, useful for pageview tracking, or updating the <title>, for example.

路由处理现在在幕后完成,当路由更改时,ReactRouter将新的props发送到Route处理程序。例如,每当路由发生变化时,路由器都会有一个新的onUpdate prop回调,对于页面视图跟踪或更新

非常有用。</p>

Client (HTML5 routing)

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)

Client (hash-based routing)

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)

Server

On the server, we can use ReactRouter.match, this is taken from the server rendering guide

在服务器上,我们可以使用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')
    }
  })
})

#1


128  

Note, this answer covers React Router version 0.13.x - the upcoming version 1.0 looks like it will have significantly different implementation details

注意,这个答案涵盖了React Router版本0.13.x - 即将推出的1.0版本看起来会有明显不同的实现细节

Server

This is a minimal server.js with react-router:

这是带有react-router的最小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})
  })
})

Where the routes module exports a list of Routes:

routes模块导出路由列表的位置:

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

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

Every time a request is made to the server, you create a single-use Router instance configured with the incoming URL as its static location, which is resolved against the tree of routes to set up the appropriate matched routes, calling back with the top-level route handler to be rendered and a record of which child routes matched at each level. This is what's consulted when you use the <RouteHandler> component within a route handling component to render a child route which was matched.

每次向服务器发出请求时,您都会创建一个一次性使用的路由器实例,该实例使用传入的URL作为其静态位置进行配置,该实例将根据路由树进行解析,以设置相应的匹配路由,并使用要呈现的级别路由处理程序以及在每个级别匹配的子路由的记录。当您在路由处理组件中使用 组件来呈现匹配的子路由时,这就是所咨询的内容。

If the user has JavaScript turned off, or it's being slow to load, any links they click on will hit the server again, which is resolved again as above.

如果用户关闭了JavaScript,或者加载速度很慢,则他们点击的任何链接都会再次点击服务器,如上所述再次解决。

Client

This is a minimal client.js with react-router (re-using the same routes module):

这是一个带有react-router的最小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)
})

When you call Router.run(), it creates a Router instance for you behind the scenes, which is re-used every time you navigate around the app, as the URL can be dynamic on the client, as opposed to on the server where a single request has a fixed URL.

当您调用Router.run()时,它会在幕后为您创建一个Router实例,每次您在应用程序中导航时都会重复使用该实例,因为URL可以在客户端上是动态的,而不是在服务器上单个请求具有固定的URL。

In this case, we're using the HistoryLocation, which uses the History API to make sure the right thing happens when you hit the back/forward button. There's also a HashLocation which changes the URL hash to make history entries and listens to the window.onhashchange event to trigger navigation.

在这种情况下,我们使用HistoryLocation,它使用History API来确保当您点击后退/前进按钮时正确的事情发生。还有一个HashLocation,它更改URL哈希以创建历史记录条目并侦听window.onhashchange事件以触发导航。

When you use react-router's <Link> component, you give it a to prop which is the name of a route, plus any params and query data the route needs. The <a> rendered by this component has an onClick handler which ultimately calls router.transitionTo() on the router instance with the props you gave the link, which looks like this:

当您使用react-router的 组件时,您可以为它提供一个prop,它是路由的名称,以及路由所需的任何params和查询数据。此组件呈现的具有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);
    }
  },

For a regular link this ultimately calls location.push() on whichever Location type you're using, which handles the details of setting up history so navigating with the back and forward buttons will work, then calls back to router.handleLocationChange() to let the router know it can proceed with transitioning to the new URL path.

对于常规链接,这最终会调用您正在使用的任何位置类型的location.push(),它处理设置历史记录的详细信息,因此使用后退和前进按钮进行导航将起作用,然后回调到router.handleLocationChange()让路由器知道它可以继续转换到新的URL路径。

The router then calls its own router.dispatch() method with the new URL, which handles the details of determining which of the configured routes match the URL, then calls any transition hooks present for the matched routes. You can implement these transition hooks on any of your route handlers to take some action when a route is about to be navigated away from or navigated to, with the ability to abort the transition if things aren't to your liking.

然后,路由器使用新URL调用自己的router.dispatch()方法,该URL处理确定哪些配置的路由与URL匹配的详细信息,然后调用匹配路由的任何转换挂钩。您可以在任何路由处理程序上实现这些转换挂钩,以便在路径即将导航或导航到路径时执行某些操作,并且能够在不符合您喜欢的情况下中止转换。

If the transition wasn't aborted, the final step is to call the callback you gave to Router.run() with the top-level handler component and a state object with all the details of the URL and the matched routes. The top-level handler component is actually the Router instance itself, which handles rendering the top-most route handler which was matched.

如果转换没有中止,最后一步是使用*处理程序组件调用您给Router.run()的回调,并使用URL和匹配路由的所有详细信息调用状态对象。*处理程序组件实际上是路由器实例本身,它处理渲染匹配的最顶层路由处理程序。

The above process is re-run every time you navigate to a new URL on the client.

每次导航到客户端上的新URL时,都会重新运行上述过程。

Example projects

#2


25  

With 1.0, React-Router depends on the history module as a peerDependency. This module deals with routing in the browser. By default React-Router uses the HTML5 History API (pushState, replaceState), but you can configure it to use hash-based routing (see below)

使用1.0,React-Router依赖于历史模块作为peerDependency。该模块处理浏览器中的路由。默认情况下,React-Router使用HTML5 History API(pushState,replaceState),但您可以将其配置为使用基于散列的路由(请参阅下文)

The route handling is now done behind the scenes, and ReactRouter sends new props down to the Route handlers when the route changes. The Router has a new onUpdate prop callback whenever a route changes, useful for pageview tracking, or updating the <title>, for example.

路由处理现在在幕后完成,当路由更改时,ReactRouter将新的props发送到Route处理程序。例如,每当路由发生变化时,路由器都会有一个新的onUpdate prop回调,对于页面视图跟踪或更新

非常有用。</p>

Client (HTML5 routing)

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)

Client (hash-based routing)

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)

Server

On the server, we can use ReactRouter.match, this is taken from the server rendering guide

在服务器上,我们可以使用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')
    }
  })
})