简体   繁体   English

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

[英]Client Routing (using react-router) and Server-Side Routing

I have been thinking and I am confused with the routing between Client and Server. 我一直在想,我对客户端和服务器之间的路由感到困惑。 Suppose I use ReactJS for server-side rendering before sending the request back to web browser, and use react-router as a client-side routing to switch between pages without refreshing as SPA. 假设我在将请求发送回Web浏览器之前使用ReactJS进行服务器端呈现,并使用react-router作为客户端路由在页面之间切换而不刷新为SPA。

What comes to mind is: 我想到的是:

  • How are the routes interpreted? 如何解释路线? For example, a request from Home page ( /home ) to Posts page ( /posts ) 例如,从主页( /home )到帖子页面( /posts )的请求
  • Where does the routing go, on server-side or client? 路由在服务器端或客户端上的位置是什么?
  • How does it know how it is processed? 它是如何知道如何处理的?

Note, this answer covers React Router version 0.13.x - the upcoming version 1.0 looks like it will have significantly different implementation details 注意,这个答案涵盖了React Router版本0.13.x - 即将推出的1.0版本看起来会有明显不同的实现细节

Server 服务器

This is a minimal server.js with react-router: 这是带有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})
  })
})

Where the routes module exports a list of Routes: routes模块导出路由列表的位置:

var React = require('react')
var {DefaultRoute, NotFoundRoute, Route} = require('react-router')

module.exports = [
  <Route path="/" handler={require('./components/App')}>
    {/* ... */}
  </Route>
]

Every time a request is made to the server, you create a single-use Router instance configured with the incoming URL as its static location, which is resolved against the tree of routes to set up the appropriate matched routes, calling back with the top-level route handler to be rendered and a record of which child routes matched at each level. 每次向服务器发出请求时,您都会创建一个一次性使用的Router实例,该实例使用传入的URL作为其静态位置进行配置,该实例将根据路由树进行解析,以设置相应的匹配路由,并使用要呈现的级别路由处理程序以及在每个级别匹配的子路由的记录。 This is what's consulted when you use the <RouteHandler> component within a route handling component to render a child route which was matched. 当您在路由处理组件中使用<RouteHandler>组件来呈现匹配的子路由时,这就是所咨询的内容。

If the user has JavaScript turned off, or it's being slow to load, any links they click on will hit the server again, which is resolved again as above. 如果用户关闭了JavaScript,或者加载速度很慢,则他们点击的任何链接都会再次点击服务器,如上所述再次解决。

Client 客户

This is a minimal client.js with react-router (re-using the same routes module): 这是一个带有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)
})

When you call Router.run() , it creates a Router instance for you behind the scenes, which is re-used every time you navigate around the app, as the URL can be dynamic on the client, as opposed to on the server where a single request has a fixed URL. 当您调用Router.run() ,它会在幕后为您创建一个Router实例,每次您在应用程序中导航时都会重复使用该实例,因为URL可以在客户端上是动态的,而不是在服务器上单个请求具有固定的URL。

In this case, we're using the HistoryLocation , which uses the History API to make sure the right thing happens when you hit the back/forward button. 在这种情况下,我们使用HistoryLocation ,它使用History API来确保当您点击后退/前进按钮时正确的事情发生。 There's also a HashLocation which changes the URL hash to make history entries and listens to the window.onhashchange event to trigger navigation. 还有一个HashLocation ,它更改URL hash以创建历史记录条目并侦听window.onhashchange事件以触发导航。

When you use react-router's <Link> component, you give it a to prop which is the name of a route, plus any params and query data the route needs. 当您使用反应路由器的<Link>组件,你给它一个to支撑这是一个路由的名称,加上任何paramsquery数据的路由需求。 The <a> rendered by this component has an onClick handler which ultimately calls router.transitionTo() on the router instance with the props you gave the link, which looks like this: 这个组件呈现的<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);
    }
  },

For a regular link this ultimately calls location.push() on whichever Location type you're using, which handles the details of setting up history so navigating with the back and forward buttons will work, then calls back to router.handleLocationChange() to let the router know it can proceed with transitioning to the new URL path. 对于常规链接,这最终会在您正在使用的任何位置类型上调用location.push() ,它处理设置历史记录的详细信息,以便使用后退和前进按钮进行导航,然后回调到router.handleLocationChange()让路由器知道它可以继续转换到新的URL路径。

The router then calls its own router.dispatch() method with the new URL, which handles the details of determining which of the configured routes match the URL, then calls any transition hooks present for the matched routes. 然后,路由器使用新URL调用自己的router.dispatch()方法,该URL处理确定哪些配置的路由与URL匹配的详细信息,然后调用匹配路由的任何转换挂钩 You can implement these transition hooks on any of your route handlers to take some action when a route is about to be navigated away from or navigated to, with the ability to abort the transition if things aren't to your liking. 您可以在任何路由处理程序上实现这些转换挂钩,以便在路由即将导航或导航到路径时执行某些操作,并且可以根据您的喜好中止转换。

If the transition wasn't aborted, the final step is to call the callback you gave to Router.run() with the top-level handler component and a state object with all the details of the URL and the matched routes. 如果转换没有中止,最后一步是使用顶级处理程序组件调用您给Router.run()的回调,并调用包含URL和匹配路由的所有详细信息的状态对象。 The top-level handler component is actually the Router instance itself, which handles rendering the top-most route handler which was matched. 顶级处理程序组件实际上是Router实例本身,它处理呈现匹配的最顶层路由处理程序。

The above process is re-run every time you navigate to a new URL on the client. 每次导航到客户端上的新URL时,都会重新运行上述过程。

Example projects 示例项目

With 1.0, React-Router depends on the history module as a peerDependency. 使用1.0,React-Router依赖于历史模块作为peerDependency。 This module deals with routing in the browser. 该模块处理浏览器中的路由。 By default React-Router uses the HTML5 History API ( pushState , replaceState ), but you can configure it to use hash-based routing (see below) 默认情况下,React-Router使用HTML5 History 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. 路由处理现在在幕后完成,当路由更改时,ReactRouter将新的props发送到Route处理程序。 The Router has a new onUpdate prop callback whenever a route changes, useful for pageview tracking, or updating the <title> , for example. 例如,每当路由发生变化时,路由器都会有一个新的onUpdate prop回调,对于页面浏览跟踪或更新<title>有用的。

Client (HTML5 routing) 客户端(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)

Client (hash-based routing) 客户端(基于散列的路由)

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)

Server 服务器

On the server, we can use ReactRouter.match , this is taken from the server rendering guide 在服务器上,我们可以使用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')
    }
  })
})

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM