客户端路由(使用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,它是一个路由的名字,加上路由所需的任何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()
来处理设置历史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( pushState
, replaceState
),但可以将其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') } }) })