【问题标题】:Client Routing (using react-router) and Server-Side Routing客户端路由(使用 react-router)和服务器端路由
【发布时间】:2015-04-17 16:48:38
【问题描述】:

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

想到的是:

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

【问题讨论】:

  • 我建议阅读浏览器中的 History API。

标签: javascript reactjs routes react-router url-routing


【解决方案1】:

请注意,此答案涵盖 React Router 版本 0.13.x - upcoming version 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 配置为其静态位置,该实例将针对路由树进行解析以设置适当的匹配路由,调用返回要呈现的顶级路由处理程序以及每个级别匹配的子路由的记录。这是您在路由处理组件中使用&lt;RouteHandler&gt; 组件来呈现匹配的子路由时会参考的内容。

如果用户关闭了 JavaScript,或者加载速度很慢,他们点击的任何链接都会再次访问服务器,并再次如上解决。

客户

这是带有 react-router 的最小 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 事件以触发导航。

当你使用 react-router 的 &lt;Link&gt; 组件时,你给它一个 to 属性,它是路由的名称,加上路由需要的任何 paramsquery 数据。该组件渲染的&lt;a&gt; 有一个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 匹配的详细信息,然后调用匹配路由的任何 transition hooks。您可以在任何路由处理程序上实现这些转换钩子,以便在路由即将被导航离开或导航到时采取一些行动,如果事情不符合您的喜好,则可以中止转换。

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

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

示例项目

【讨论】:

  • 所以我可能会说客户端路由是由 javascript(这是 react-router 代码)处理的,如果它存在的话。每当我在浏览器地址栏上按回车键或刷新页面或禁用 JS 时,服务器端都会处理路由。另一方面,当当前页面上的 javascript 准备就绪时,路由将由客户端处理。我理解正确吗?
  • 路由模块var routes = require('./routes')是什么,是路由列表吗?我使用过 Express 路由器,但是这里的这个示例似乎是使用 React 路由器设置服务器端渲染的唯一示例,所以如果它是一个完整的代码示例会很好
  • 应该是路由列表。我将为此添加一条注释以及一些指向示例项目的链接。
  • 那么,如果 react-router 负责服务器端路由,那么谁与数据库通信?服务器端路由会发生什么?假设我们想为原生移动应用程序提供 REST API。谁来处理?
  • 由于react-router 版本较新,答案已过时。请更新。
【解决方案2】:

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

路由处理现在在幕后完成,当路由发生变化时,ReactRouter 将新的 props 发送到 Route 处理程序。每当路由发生变化时,Router 都会有一个新的 onUpdate 属性回调,这对于网页浏览跟踪或更新 &lt;title&gt; 很有用。

客户端(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,这个取自server rendering guide

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')
    }
  })
})

【讨论】:

    猜你喜欢
    • 2018-02-05
    • 2014-07-21
    • 1970-01-01
    • 2016-10-06
    • 1970-01-01
    • 2016-06-01
    • 2016-09-24
    • 2017-07-30
    • 2018-02-25
    相关资源
    最近更新 更多