客户端路由(使用反应路由器)和服务器端路由

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

我想到的是:

  • 路由是如何解释的? 例如,请求从主页(/home)到邮件页(/posts)
  • 路由通向哪里,服务器端还是客户端?
  • 它如何知道它是如何处理的?
77671 次浏览

注意,这个答案涵盖了 React 路由器版本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

这是一个最小的 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()时,它会在后台为您创建一个路由器实例,每次您在应用程序中导航时都会重用这个实例,因为 URL 在客户机上是动态的,而在服务器上,一个请求有一个固定的 URL。

在本例中,我们使用 HistoryLocation,它使用 History API来确保在您按下后退/前进按钮时发生正确的事情。还有一个 HashLocation,它更改 URL hash以创建历史条目,并监听 window.onhashchange事件以触发导航。

当您使用反应路由器的 <Link>组件时,您给它一个 to道具,它是路由器的名称,加上路由器需要的任何 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(),处理设置历史记录的细节,以便使用后退和前进按钮导航,然后回调到 router.handleLocationChange(),让路由器知道它可以继续过渡到新的 URL 路径。

然后,路由器使用新 URL 调用自己的 router.dispatch()方法,该方法处理确定哪些配置的路由与 URL 匹配的细节,然后调用匹配路由的任何 过渡挂钩。您可以在任何路由处理程序上实现这些转换钩子,以便在路由即将被导航离开或导航到的时候采取某些操作,如果事情不合您的意,则可以中止转换。

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

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

示例项目

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

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.

客户端(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')
}
})
})