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

116

我一直在思考并且对客户端和服务器之间的路由感到困惑。假设我使用ReactJS进行服务器端渲染,然后将请求发送回Web浏览器,并使用React-Router作为客户端路由,在SPA中实现页面切换而无需刷新。

我想到的问题是:

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

1
我建议阅读浏览器中的历史 API。 - WiredPrairie
2个回答

139
注意,本答案涵盖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>
]

每次向服务器发出请求时,您都会创建一个单次使用的Router实例,并将传入的URL配置为其静态位置,该位置针对路由树进行解析以设置适当的匹配路由,回调顶级路由处理程序以呈现并记录每个级别匹配的子路由。当您在路由处理组件中使用<RouteHandler>组件呈现匹配的子路由时,这是要查阅的内容。
如果用户关闭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()时,它将在幕后为您创建一个Router实例,每次在应用程序中导航时都会重复使用该实例,因为在客户端上URL可以是动态的,而不像在服务器上单个请求有一个固定的URL。
在这种情况下,我们正在使用HistoryLocation,它使用History API确保在点击后退/前进按钮时发生正确的事情。 还有一个HashLocation,它更改URL的哈希,以使历史记录条目,并侦听window.onhashchange事件来触发导航。
当你使用react-router的<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类型上调用location.push(),该方法处理设置历史记录以便在使用后退和前进按钮进行导航时正常工作的详细信息,并回调router.handleLocationChange(),以让路由器知道它可以继续转换为新的URL路径。
然后,路由器使用新URL调用其自己的router.dispatch()方法,该方法处理确定哪些已配置的路由与URL匹配的详细信息,然后调用匹配路由中存在的任何transition hooks。您可以在任何路由处理程序上实现这些过渡钩子,以在即将从路由导航或导航到路由时执行某些操作,并能够根据需要中止过渡。
如果没有中止过渡,则最后一步是使用顶级处理程序组件和包含URL及匹配路由的所有详细信息的状态对象调用您提供给Router.run()的回调函数。顶级处理程序组件实际上是Router实例本身,它处理呈现与匹配的顶级路由处理程序。
每次在客户端导航到新的URL时,都会重新运行上述过程。

示例项目


3
如果有 JavaScript(也就是 react-router 代码),我可以说客户端路由将由 JavaScript 处理。当我在浏览器地址栏输入回车、刷新页面或禁用 JS 时,服务器端将处理路由。另一方面,当当前页面的 JavaScript 就绪时,路由将由客户端处理。你的理解正确吗? - heartmon
9
var routes = require('./routes') 模块中包含了什么?它是一个路由列表吗?我已经使用过 Express 路由器,但在 SO 上的这个例子似乎是使用 React Router 设置服务器端渲染的唯一例子,所以如果有完整的代码示例就更好了。 - svnm
2
这应该是一个路由列表。我会加上一些注释以及一些示例项目的链接。 - Jonny Buchanan
2
那么如果React-Router负责服务器端路由,那么谁来与数据库通信?服务器端路由会发生什么变化?假设我们想为本机移动应用程序提供REST API。谁来负责这个? - Morteza Shahriari Nia
1
答案已过时,因为有更新的 react-router 版本。请进行更新。 - oleh.meleshko
显示剩余3条评论

27

React-Router 1.0现在作为对等的依赖项使用history模块来处理浏览器路由。默认情况下,React-Router使用HTML5 History API (pushState, replaceState)进行路由处理,但你可以配置它使用基于哈希值的路由(见下文)。

现在路由处理在幕后完成,当路由变化时,ReactRouter会向路由处理程序发送新的props。路由器有一个新的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')
    }
  })
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接