首页 文章

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

提问于
浏览
102

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

我想到的是:

  • 如何解释路线?例如,从主页( /home )到帖子页面( /posts )的请求

  • 路由在服务器端或客户端上的位置是什么?

  • 它如何知道如何处理?

2 回答

  • 25

    注意,这个答案涵盖了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作为其静态位置进行配置,该实例将根据路由树进行解析,以设置相应的匹配路由,并使用top-要呈现的级别路由处理程序以及在每个级别匹配的子路由的记录 . 当您在路径处理组件中使用 <RouteHandler> 组件来呈现匹配的子路由时,这就是所咨询的内容 .

    如果用户关闭了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的 <Link> 组件时,您会给它一个 to 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() ,它会处理设置历史记录的详细信息,因此使用后退和前进按钮进行导航将起作用,然后回调 router.handleLocationChange() 让路由器知道它可以继续转换到新的URL路径 .

    然后,路由器使用新URL调用自己的 router.dispatch() 方法,该URL处理确定哪些配置的路由与URL匹配的详细信息,然后调用匹配路由的任何transition hooks present . 您可以在任何路由处理程序上实现这些转换挂钩,以便在路径即将导航或导航到路径时执行某些操作,并且能够在不符合您喜欢的情况下中止转换 .

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

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

    示例项目

  • 130

    使用1.0时,React-Router依赖于history模块作为peerDependency . 该模块处理浏览器中的路由 . 默认情况下,React-Router使用HTML5历史记录API( pushStatereplaceState ),但您可以将其配置为使用基于散列的路由(请参阅下文)

    路由处理现在在幕后完成,当路由更改时,ReactRouter将新的props发送到Route处理程序 . 例如,每当路由发生变化时,路由器都会有一个新的 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 ,这取自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')
        }
      })
    })
    

相关问题