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

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

想到的是:

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

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

服务器

这是一个与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}) }) }) 

routes模块在哪里输出一个路由列表:

 var React = require('react') var {DefaultRoute, NotFoundRoute, Route} = require('react-router') module.exports = [ <Route path="/" handler={require('./components/App')}> {/* ... */} </Route> ] 

每次向服务器发出请求时,都会创build一个configuration为传入URL的单次使用的Router实例作为其静态位置,这将针对路由树进行parsing以设置适当的匹配路由,级别路由处理程序将被渲染,并logging每个级别的哪些子路由匹配。 当您在路由处理组件中使用<RouteHandler>组件来呈现匹配的子路由时,会查阅这些内容。

如果用户closures了JavaScript,或者加载速度很慢,那么他们点击的任何链接都会再次触发服务器,这将再次按上述方式解决。

客户

这是一个最小的client.js与react-router(重新使用相同的路由模块):

 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() ,它会在幕后为你创build一个Router实例,每当你浏览应用程序的时候都会重新使用这个实例,因为URL可以是客户端上的dynamic地址,而不是服务器上的地址一个请求有一个固定的URL。

在这种情况下,我们使用HistoryLocation ,使用History API来确保当您点击后退/前进button时,正确的事情发生。 还有一个HashLocation ,它可以改变URL hash来创build历史条目,并监听window.onhashchange事件来触发导航。

当你使用react-router的<Link>组件的时候,你给它一个prop,它是一个路由的名字,加上路由所需的任何paramsquery数据。 由这个组件呈现的<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()来处理设置历史logging的细节,以便使用后退和前进button进行导航,然后callback到router.handleLocationChange()让路由器知道它可以继续转换到新的URLpath。

然后,路由器使用新的URL调用自己的router.dispatch()方法,该URL处理确定哪些configuration的路由与URL匹配的详细信息,然后调用匹配路由的所有过渡钩子 。 您可以在任何路由处理程序上实现这些转换挂钩,以便在路线即将导航离开或导航到时执行一些操作,如果事情不符合您的需要,可以中止转换。

如果转换没有被中止,最后一步就是调用你给的callbackRouter.run() ,它包含顶级处理程序组件和一个包含URL和匹配路由的详细信息的状态对象。 顶级处理程序组件实际上是Router实例本身,它处理呈现匹配的最顶级路由处理程序。

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

示例项目

  • 反应路由器超级演示
  • 同构实验室

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

路由处理现在在幕后完成,当路由改变时,ReactRouter将新的道具发送到Route处理程序。 例如,每当路由发生变化时,路由器都会有一个新的onUpdate propcallback,用于pageview跟踪或者更新<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') } }) })