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


108

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

我想到的是:

  • 路线如何解释?例如,从首页(/home)到帖子页面(/posts)的请求
  • 路由在服务器端还是客户端去哪里?
  • 它如何知道如何处理?

1
我建议在浏览器中阅读History API。
WiredPrairie

Answers:


137

注意,此答案涵盖了React Router版本0.13.x- 即将发布的版本1.0看起来将具有明显不同的实现细节

服务器

server.js对于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})
  })
})

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.jsreact-router 的最小要求(重新使用相同的routes模块):

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可以在客户端上动态化,而在服务器上单个请求具有动态固定网址。

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

当您使用反应路由器的<Link>成分,你给它一个to道具是一个路由的名称,以及任何paramsquery数据路由需求。该<a>组件渲染的onClick处理程序最终会router.transitionTo()使用您赋予链接的props 调用路由器实例,如下所示:

  /**
   * 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处理(如果有的话,它是react-router代码)。每当我在浏览器地址栏上按Enter或刷新页面或禁用JS时,服务器端都会处理路由。另一方面,当JavaScript在当前页面上准备就绪时,路由将由客户端处理。我理解正确吗?
heartmon

9
路由模块中的内容var routes = require('./routes')是路由列表吗?我使用过Express路由器,但是这里的这个示例似乎是使用React Router设置服务器端渲染的唯一示例,因此如果它是完整的代码示例,那就很好了
svnm 2015年

2
它应该是路线列表。我将为此添加一条注释以及一些示例项目的链接。
强尼·布坎南

2
因此,如果react-router负责服务器端路由,那么谁与数据库对话呢?服务器端路由会发生什么?想象我们想为本地移动应用程序提供REST API。谁来照顾这个?
Morteza Shahriari Nia

1
由于版本较新,答案已过时react-router。请更新。
oleh.meleshko,2016年

26

在1.0中,React-Router依赖于历史模块作为peerDependency。该模块处理浏览器中的路由。默认情况下,React-Router使用HTML5历史记录API(pushStatereplaceState),但是您可以将其配置为使用基于哈希的路由(请参见下文)

现在,在后台进行了路线处理,并且当路线更改时,ReactRouter将新的道具发送给Route处理程序。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')
    }
  })
})
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.