路由配置

路由配置是一组指令,用来告诉 router 如何匹配 URL以及匹配后如何执行代码。我们来通过一个简单的例子解释一下如何编写路由配置。

  1. import React from 'react'
  2. import { render } from 'react-dom'
  3. import { Router, Route, Link } from 'react-router'
  4. const App = React.createClass({
  5. render() {
  6. return (
  7. <div>
  8. <h1>App</h1>
  9. <ul>
  10. <li><Link to="/about">About</Link></li>
  11. <li><Link to="/inbox">Inbox</Link></li>
  12. </ul>
  13. {this.props.children}
  14. </div>
  15. )
  16. }
  17. })
  18. const About = React.createClass({
  19. render() {
  20. return <h3>About</h3>
  21. }
  22. })
  23. const Inbox = React.createClass({
  24. render() {
  25. return (
  26. <div>
  27. <h2>Inbox</h2>
  28. {this.props.children || "Welcome to your Inbox"}
  29. </div>
  30. )
  31. }
  32. })
  33. const Message = React.createClass({
  34. render() {
  35. return <h3>Message {this.props.params.id}</h3>
  36. }
  37. })
  38. render((
  39. <Router>
  40. <Route path="/" component={App}>
  41. <Route path="about" component={About} />
  42. <Route path="inbox" component={Inbox}>
  43. <Route path="messages/:id" component={Message} />
  44. </Route>
  45. </Route>
  46. </Router>
  47. ), document.body)

通过上面的配置,这个应用知道如何渲染下面四个 URL:

URL 组件
/ App
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

添加首页

想象一下当 URL 为 / 时,我们想渲染一个在 App 中的组件。不过在此时,Apprender 中的 this.props.children 还是 undefined。这种情况我们可以使用 IndexRoute 来设置一个默认页面。

  1. import { IndexRoute } from 'react-router'
  2. const Dashboard = React.createClass({
  3. render() {
  4. return <div>Welcome to the app!</div>
  5. }
  6. })
  7. render((
  8. <Router>
  9. <Route path="/" component={App}>
  10. {/* 当 url 为/时渲染 Dashboard */}
  11. <IndexRoute component={Dashboard} />
  12. <Route path="about" component={About} />
  13. <Route path="inbox" component={Inbox}>
  14. <Route path="messages/:id" component={Message} />
  15. </Route>
  16. </Route>
  17. </Router>
  18. ), document.body)

现在,Apprender 中的 this.props.children 将会是 <Dashboard>这个元素。这个功能类似 Apache 的DirectoryIndex 以及 nginx的 index指令,上述功能都是在当请求的 URL 匹配某个目录时,允许你制定一个类似index.html的入口文件。

我们的 sitemap 现在看起来如下:

URL 组件
/ App -> Dashboard
/about App -> About
/inbox App -> Inbox
/inbox/messages/:id App -> Inbox -> Message

让 UI 从 URL 中解耦出来

如果我们可以将 /inbox/inbox/messages/:id 中去除,并且还能够让 Message 嵌套在 App -> Inbox 中渲染,那会非常赞。绝对路径可以让我们做到这一点。

  1. render((
  2. <Router>
  3. <Route path="/" component={App}>
  4. <IndexRoute component={Dashboard} />
  5. <Route path="about" component={About} />
  6. <Route path="inbox" component={Inbox}>
  7. {/* 使用 /messages/:id 替换 messages/:id */}
  8. <Route path="/messages/:id" component={Message} />
  9. </Route>
  10. </Route>
  11. </Router>
  12. ), document.body)

在多层嵌套路由中使用绝对路径的能力让我们对 URL 拥有绝对的掌控。我们无需在 URL 中添加更多的层级,从而可以使用更简洁的 URL。

我们现在的 URL 对应关系如下:

URL 组件
/ App -> Dashboard
/about App -> About
/inbox App -> Inbox
/messages/:id App -> Inbox -> Message

提醒:绝对路径可能在动态路由中无法使用。

兼容旧的 URL

等一下,我们刚刚改变了一个 URL! 这样不好。 现在任何人访问 /inbox/messages/5 都会看到一个错误页面。:(

不要担心。我们可以使用 <Redirect> 使这个 URL 重新正常工作。

  1. import { Redirect } from 'react-router'
  2. render((
  3. <Router>
  4. <Route path="/" component={App}>
  5. <IndexRoute component={Dashboard} />
  6. <Route path="about" component={About} />
  7. <Route path="inbox" component={Inbox}>
  8. <Route path="/messages/:id" component={Message} />
  9. {/* 跳转 /inbox/messages/:id 到 /messages/:id */}
  10. <Redirect from="messages/:id" to="/messages/:id" />
  11. </Route>
  12. </Route>
  13. </Router>
  14. ), document.body)

现在当有人点击 /inbox/messages/5 这个链接,他们会被自动跳转到 /messages/5:raised_hands:

进入和离开的Hook

Route 可以定义 onEnteronLeave 两个 hook ,这些hook会在页面跳转确认时触发一次。这些 hook 对于一些情况非常的有用,例如权限验证或者在路由跳转前将一些数据持久化保存起来。

在路由跳转过程中,onLeave hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter hook会从最外层的父路由开始直到最下层子路由结束。

继续我们上面的例子,如果一个用户点击链接,从 /messages/5 跳转到 /about,下面是这些 hook 的执行顺序:

  • /messages/:idonLeave
  • /inboxonLeave
  • /aboutonEnter

替换的配置方式

因为 route 一般被嵌套使用,所以使用 JSX 这种天然具有简洁嵌套型语法的结构来描述它们的关系非常方便。然而,如果你不想使用 JSX,也可以直接使用原生 route 数组对象。

上面我们讨论的路由配置可以被写成下面这个样子:

  1. const routeConfig = [
  2. { path: '/',
  3. component: App,
  4. indexRoute: { component: Dashboard },
  5. childRoutes: [
  6. { path: 'about', component: About },
  7. { path: 'inbox',
  8. component: Inbox,
  9. childRoutes: [
  10. { path: '/messages/:id', component: Message },
  11. { path: 'messages/:id',
  12. onEnter: function (nextState, replaceState) {
  13. replaceState(null, '/messages/' + nextState.params.id)
  14. }
  15. }
  16. ]
  17. }
  18. ]
  19. }
  20. ]
  21. render(<Router routes={routeConfig} />, document.body)